/*
 * Dongle BUS interface
 * USB Linux Implementation
 *
 * Copyright (C) 1999-2016, Broadcom Corporation
 *
 *      Unless you and Broadcom execute a separate written software license
 * agreement governing use of this software, this software is licensed to you
 * under the terms of the GNU General Public License version 2 (the "GPL"),
 * available at http://www.broadcom.com/licenses/GPLv2.php, with the
 * following added to such license:
 *
 *      As a special exception, the copyright holders of this software give you
 * permission to link this software with independent modules, and to copy and
 * distribute the resulting executable under terms of your choice, provided that
 * you also meet, for each linked independent module, the terms and conditions
 * of the license of that module.  An independent module is a module which is
 * not derived from this software.  The special exception does not apply to any
 * modifications of the software.
 *
 *      Notwithstanding the above, under no circumstances may you combine this
 * software in any way with any other Broadcom software provided under a license
 * other than the GPL, without Broadcom's express prior written consent.
 *
 *
 * <<Broadcom-WL-IPTag/Open:>>
 *
 * $Id: dbus_usb_linux.c 564663 2015-06-18 02:34:42Z $
 */

/**
 * @file @brief
 * This file contains DBUS code that is USB *and* OS (Linux) specific. DBUS is a
 * Broadcom proprietary host specific abstraction layer.
 */

#include <typedefs.h>
#include <osl.h>

/**
 * DBUS_LINUX_RXDPC is created for router platform performance tuning. A
 * separate thread is created to handle USB RX and avoid the call chain getting
 * too long and enhance cache hit rate.
 *
 * DBUS_LINUX_RXDPC setting is in wlconfig file.
 */

/*
 * If DBUS_LINUX_RXDPC is off, spin_lock_bh() for CTFPOOL in
 * linux_osl.c has to be changed to spin_lock_irqsave() because
 * PKTGET/PKTFREE are no longer in bottom half.
 *
 * Right now we have another queue rpcq in wl_linux.c. Maybe we
 * can eliminate that one to reduce the overhead.
 *
 * Enabling 2nd EP and DBUS_LINUX_RXDPC causing traffic from
 * both EP's to be queued in the same rx queue. If we want
 * RXDPC to work with 2nd EP. The EP for RPC call return
 * should bypass the dpc and go directly up.
 */

#include <usbrdl.h>
#include <bcmendian.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/usb.h>
#include <linux/skbuff.h>
#include <linux/netdevice.h>
#include <linux/random.h>
#include <linux/spinlock.h>
#include <linux/list.h>
#include <asm/uaccess.h>
#include <asm/unaligned.h>
#include <dbus.h>
#include <bcmutils.h>
#include <bcmdevs.h>
#include <linux/usb.h>
#include <usbrdl.h>
#include <linux/firmware.h>
#include <dngl_stats.h>
#include <dhd.h>

#if defined(USBOS_THREAD) || defined(USBOS_TX_THREAD)

/**
 * The usb-thread is designed to provide currency on multiprocessors and SMP
 * linux kernels. On the dual cores platform, the WLAN driver, without threads,
 * executed only on CPU0. The driver consumed almost of 100% on CPU0, while CPU1
 * remained idle. The behavior was observed on Broadcom's STB.
 *
 * The WLAN driver consumed most of CPU0 and not CPU1 because tasklets/queues,
 * software irq, and hardware irq are executing from CPU0, only. CPU0 became the
 * system's bottle-neck. TPUT is lower and system's responsiveness is slower.
 *
 * To improve system responsiveness and TPUT usb-thread was implemented. The
 * system's threads could be scheduled to run on any core. One core could be
 * processing data in the usb-layer and the other core could be processing data
 * in the wl-layer.
 *
 * For further info see [WlThreadAndUsbThread] Twiki.
 */

#include <linux/kthread.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <asm/hardirq.h>
#include <linux/list.h>
#include <linux_osl.h>
#endif /* USBOS_THREAD || USBOS_TX_THREAD */

#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 0))
#define KERNEL26
#endif

/**
 * Starting with the 3.10 kernel release, dynamic PM support for USB is present
 * whenever the kernel was built with CONFIG_PM_RUNTIME enabled. The
 * CONFIG_USB_SUSPEND option has been eliminated.
 */
#if ((LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 21)) &&                       \
     defined(CONFIG_USB_SUSPEND)) ||                                           \
    ((LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 0)) &&                       \
     defined(CONFIG_PM_RUNTIME)) ||                                            \
    (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 19, 0))
/* For USB power management support, see Linux kernel:
 * Documentation/usb/power-management.txt */
#define USB_SUSPEND_AVAILABLE
#endif

/* Define alternate fw/nvram paths used in Android */
#ifdef OEM_ANDROID
#define CONFIG_ANDROID_BCMDHD_FW_PATH "broadcom/dhd/firmware/fw.bin.trx"
#define CONFIG_ANDROID_BCMDHD_NVRAM_PATH "broadcom/dhd/nvrams/nvm.txt"
#endif /* OEM_ANDROID */

static inline int usb_submit_urb_linux(struct urb *urb)
{
#ifdef BCM_MAX_URB_LEN
    if (urb && (urb->transfer_buffer_length > BCM_MAX_URB_LEN)) {
        DBUSERR(("URB transfer length=%d exceeded %d ra=%p\n",
                 urb->transfer_buffer_length, BCM_MAX_URB_LEN,
                 __builtin_return_address(0)));
        return DBUS_ERR;
    }
#endif

#ifdef KERNEL26
    return usb_submit_urb(urb, GFP_ATOMIC);
#else
    return usb_submit_urb(urb);
#endif
}

#define USB_SUBMIT_URB(urb) usb_submit_urb_linux(urb)

#ifdef KERNEL26

#define USB_ALLOC_URB() usb_alloc_urb(0, GFP_ATOMIC)
#define USB_UNLINK_URB(urb) (usb_kill_urb(urb))
#define USB_FREE_URB(urb) (usb_free_urb(urb))
#define USB_REGISTER() usb_register(&dbus_usbdev)
#define USB_DEREGISTER() usb_deregister(&dbus_usbdev)

#ifdef USB_SUSPEND_AVAILABLE

#if (LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 33))
#define USB_AUTOPM_SET_INTERFACE(intf) usb_autopm_set_interface(intf)
#else
#define USB_ENABLE_AUTOSUSPEND(udev) usb_enable_autosuspend(udev)
#define USB_DISABLE_AUTOSUSPEND(udev) usb_disable_autosuspend(udev)
#endif /* LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 33))  */

#define USB_AUTOPM_GET_INTERFACE(intf) usb_autopm_get_interface(intf)
#define USB_AUTOPM_PUT_INTERFACE(intf) usb_autopm_put_interface(intf)
#define USB_AUTOPM_GET_INTERFACE_ASYNC(intf)                                   \
    usb_autopm_get_interface_async(intf)
#define USB_AUTOPM_PUT_INTERFACE_ASYNC(intf)                                   \
    usb_autopm_put_interface_async(intf)
#define USB_MARK_LAST_BUSY(dev) usb_mark_last_busy(dev)

#else /* USB_SUSPEND_AVAILABLE */

#define USB_AUTOPM_GET_INTERFACE(intf)                                         \
    do {                                                                       \
    } while (0)
#define USB_AUTOPM_PUT_INTERFACE(intf)                                         \
    do {                                                                       \
    } while (0)
#define USB_AUTOPM_GET_INTERFACE_ASYNC(intf)                                   \
    do {                                                                       \
    } while (0)
#define USB_AUTOPM_PUT_INTERFACE_ASYNC(intf)                                   \
    do {                                                                       \
    } while (0)
#define USB_MARK_LAST_BUSY(dev)                                                \
    do {                                                                       \
    } while (0)
#endif /* USB_SUSPEND_AVAILABLE */

#define USB_CONTROL_MSG(dev, pipe, request, requesttype, value, index, data,   \
                        size, timeout)                                         \
    usb_control_msg((dev), (pipe), (request), (requesttype), (value), (index), \
                    (data), (size), (timeout))
#define USB_BULK_MSG(dev, pipe, data, len, actual_length, timeout)             \
    usb_bulk_msg((dev), (pipe), (data), (len), (actual_length), (timeout))
#define USB_BUFFER_ALLOC(dev, size, mem, dma)                                  \
    usb_buffer_alloc(dev, size, mem, dma)
#define USB_BUFFER_FREE(dev, size, data, dma)                                  \
    usb_buffer_free(dev, size, data, dma)

#ifdef WL_URB_ZPKT
#define URB_QUEUE_BULK URB_ZERO_PACKET
#else
#define URB_QUEUE_BULK 0
#endif /* WL_URB_ZPKT */

#define CALLBACK_ARGS struct urb *urb, struct pt_regs *regs
#define CALLBACK_ARGS_DATA urb, regs
#define CONFIGDESC(usb) (&((usb)->actconfig)->desc)
#define IFPTR(usb, idx) ((usb)->actconfig->interface[idx])
#define IFALTS(usb, idx) (IFPTR((usb), (idx))->altsetting[0])
#define IFDESC(usb, idx) IFALTS((usb), (idx)).desc
#define IFEPDESC(usb, idx, ep) (IFALTS((usb), (idx)).endpoint[ep]).desc

#else /* KERNEL26 */

#define USB_ALLOC_URB() usb_alloc_urb(0)
#define USB_UNLINK_URB(urb) usb_unlink_urb(urb)
#define USB_FREE_URB(urb) (usb_free_urb(urb))
#define USB_REGISTER() usb_register(&dbus_usbdev)
#define USB_DEREGISTER() usb_deregister(&dbus_usbdev)
#define USB_AUTOPM_GET_INTERFACE(intf)                                         \
    do {                                                                       \
    } while (0)
#define USB_AUTOPM_GET_INTERFACE_ASYNC(intf)                                   \
    do {                                                                       \
    } while (0)
#define USB_AUTOPM_PUT_INTERFACE_ASYNC(intf)                                   \
    do {                                                                       \
    } while (0)
#define USB_MARK_LAST_BUSY(dev)                                                \
    do {                                                                       \
    } while (0)

#define USB_CONTROL_MSG(dev, pipe, request, requesttype, value, index, data,   \
                        size, timeout)                                         \
    usb_control_msg((dev), (pipe), (request), (requesttype), (value), (index), \
                    (data), (size), (timeout))
#define USB_BUFFER_ALLOC(dev, size, mem, dma) kmalloc(size, mem)
#define USB_BUFFER_FREE(dev, size, data, dma) kfree(data)

#ifdef WL_URB_ZPKT
#define URB_QUEUE_BULK USB_QUEUE_BULK | URB_ZERO_PACKET
#else
#define URB_QUEUE_BULK 0
#endif /*  WL_URB_ZPKT */

#define CALLBACK_ARGS struct urb *urb
#define CALLBACK_ARGS_DATA urb
#define CONFIGDESC(usb) ((usb)->actconfig)
#define IFPTR(usb, idx) (&(usb)->actconfig->interface[idx])
#define IFALTS(usb, idx) ((usb)->actconfig->interface[idx].altsetting[0])
#define IFDESC(usb, idx) IFALTS((usb), (idx))
#define IFEPDESC(usb, idx, ep) (IFALTS((usb), (idx)).endpoint[ep])

#endif /* KERNEL26 */

#if (LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 31))
#define USB_SPEED_SUPER 5
#endif /* #if (LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 31)) */

#define CONTROL_IF 0
#define BULK_IF 0

#ifdef BCMUSBDEV_COMPOSITE
#define USB_COMPIF_MAX 4

#define USB_CLASS_WIRELESS 0xe0
#define USB_CLASS_MISC 0xef
#define USB_SUBCLASS_COMMON 0x02
#define USB_PROTO_IAD 0x01
#define USB_PROTO_VENDOR 0xff

#define USB_QUIRK_NO_SET_INTF 0x04 /* device does not support set_interface */
#endif                             /* BCMUSBDEV_COMPOSITE */

#define USB_SYNC_WAIT_TIMEOUT 300 /* ms */

/* Private data kept in skb */
#define SKB_PRIV(skb, idx) (&((void **)skb->cb)[idx])
#define SKB_PRIV_URB(skb) (*(struct urb **)SKB_PRIV(skb, 0))

#ifndef DBUS_USB_RXQUEUE_BATCH_ADD
/* items to add each time within limit */
#define DBUS_USB_RXQUEUE_BATCH_ADD 8
#endif

#ifndef DBUS_USB_RXQUEUE_LOWER_WATERMARK
/* add a new batch req to rx queue when waiting item count reduce to this number
 */
#define DBUS_USB_RXQUEUE_LOWER_WATERMARK 4
#endif

enum usbos_suspend_state {
    USBOS_SUSPEND_STATE_DEVICE_ACTIVE =
        0, /* Device is busy, won't allow suspend */
    USBOS_SUSPEND_STATE_SUSPEND_PENDING, /* Device is idle, can be suspended */
                                         /* Wating PM to suspend */
    USBOS_SUSPEND_STATE_SUSPENDED        /* Device suspended */
};

enum usbos_request_state {
    USBOS_REQUEST_STATE_UNSCHEDULED = 0, /* USB TX request not scheduled */
    USBOS_REQUEST_STATE_SCHEDULED,       /* USB TX request given to TX thread */
    USBOS_REQUEST_STATE_SUBMITTED        /* USB TX request submitted */
};

typedef struct {
    uint32 notification;
    uint32 reserved;
} intr_t;

typedef struct {
    dbus_pub_t *pub;

    void *cbarg;
    dbus_intf_callbacks_t *cbs;

    /* Imported */
    struct usb_device *usb; /* USB device pointer from OS */
    struct urb *intr_urb;   /* URB for interrupt endpoint */
    struct list_head req_rxfreeq;
    struct list_head req_txfreeq;
    struct list_head req_rxpostedq; /* Posted down to USB driver for RX */
    struct list_head req_txpostedq; /* Posted down to USB driver for TX */
    spinlock_t rxfree_lock;         /* Lock for rx free list */
    spinlock_t txfree_lock;         /* Lock for tx free list */
    spinlock_t rxposted_lock;       /* Lock for rx posted list */
    spinlock_t txposted_lock;       /* Lock for tx posted list */
    uint rx_pipe, tx_pipe, intr_pipe, rx_pipe2; /* Pipe numbers for USB I/O */
    uint rxbuf_len;

    struct list_head req_rxpendingq; /* RXDPC: Pending for dpc to send up */
    spinlock_t rxpending_lock;       /* RXDPC: Lock for rx pending list */
    long dpc_pid;
    struct semaphore dpc_sem;
    struct completion dpc_exited;
    int rxpending;

    struct urb *ctl_urb;
    int ctl_in_pipe, ctl_out_pipe;
    struct usb_ctrlrequest ctl_write;
    struct usb_ctrlrequest ctl_read;
    struct semaphore ctl_lock; /* Lock for CTRL transfers via tx_thread */
#ifdef USBOS_TX_THREAD
    enum usbos_request_state ctl_state;
#endif /* USBOS_TX_THREAD */

    spinlock_t rxlock; /* Lock for rxq management */
    spinlock_t txlock; /* Lock for txq management */

    int intr_size; /* Size of interrupt message */
    int interval;  /* Interrupt polling interval */
    intr_t intr;   /* Data buffer for interrupt endpoint */

    int maxps;
    atomic_t txposted;
    atomic_t rxposted;
    atomic_t txallocated;
    atomic_t rxallocated;
    bool rxctl_deferrespok; /* Get a response for setup from dongle */

    wait_queue_head_t wait;
    bool waitdone;
    int sync_urb_status;

    struct urb *blk_urb; /* Used for downloading embedded image */

#ifdef USBOS_THREAD
    spinlock_t ctrl_lock;
    spinlock_t usbos_list_lock;
    struct list_head usbos_list;
    struct list_head usbos_free_list;
    atomic_t usbos_list_cnt;
    wait_queue_head_t usbos_queue_head;
    struct task_struct *usbos_kt;
#endif /* USBOS_THREAD */

#ifdef USBOS_TX_THREAD
    spinlock_t usbos_tx_list_lock;
    struct list_head usbos_tx_list;
    wait_queue_head_t usbos_tx_queue_head;
    struct task_struct *usbos_tx_kt;
#endif /* USBOS_TX_THREAD */

    struct dma_pool *qtd_pool;     /* QTD pool for USB optimization only */
    int tx_ep, rx_ep, rx2_ep;      /* EPs for USB optimization */
    struct usb_device *usb_device; /* USB device for optimization */
} usbos_info_t;

typedef struct urb_req {
    void *pkt;
    int buf_len;
    struct urb *urb;
    void *arg;
    usbos_info_t *usbinfo;
    struct list_head urb_list;
} urb_req_t;

#ifdef USBOS_THREAD
typedef struct usbos_list_entry {
    struct list_head list; /* must be first */
    void *urb_context;
    int urb_length;
    int urb_status;
} usbos_list_entry_t;

static void *dbus_usbos_thread_init(usbos_info_t *usbos_info);
static void dbus_usbos_thread_deinit(usbos_info_t *usbos_info);
static void dbus_usbos_dispatch_schedule(CALLBACK_ARGS);
static int dbus_usbos_thread_func(void *data);
#endif /* USBOS_THREAD */

#ifdef USBOS_TX_THREAD
void *dbus_usbos_tx_thread_init(usbos_info_t *usbos_info);
void dbus_usbos_tx_thread_deinit(usbos_info_t *usbos_info);
int dbus_usbos_tx_thread_func(void *data);
#endif /* USBOS_TX_THREAD */

/* Shared Function prototypes */
bool dbus_usbos_dl_cmd(usbos_info_t *usbinfo, uint8 cmd, void *buffer,
                       int buflen);
int dbus_usbos_wait(usbos_info_t *usbinfo, uint16 ms);
bool dbus_usbos_dl_send_bulk(usbos_info_t *usbinfo, void *buffer, int len);
int dbus_write_membytes(usbos_info_t *usbinfo, bool set, uint32 address,
                        uint8 *data, uint size);

/* Local function prototypes */
static void dbus_usbos_send_complete(CALLBACK_ARGS);
static void dbus_usbos_recv_complete(CALLBACK_ARGS);
static int dbus_usbos_errhandler(void *bus, int err);
static int dbus_usbos_state_change(void *bus, int state);
static void dbusos_stop(usbos_info_t *usbos_info);

#ifdef KERNEL26
static int dbus_usbos_probe(struct usb_interface *intf,
                            const struct usb_device_id *id);
static void dbus_usbos_disconnect(struct usb_interface *intf);
#if defined(USB_SUSPEND_AVAILABLE)
static int dbus_usbos_resume(struct usb_interface *intf);
static int dbus_usbos_suspend(struct usb_interface *intf, pm_message_t message);
/* at the moment, used for full dongle host driver only */
static int dbus_usbos_reset_resume(struct usb_interface *intf);
#endif /* USB_SUSPEND_AVAILABLE */
#else  /* KERNEL26 */
static void *dbus_usbos_probe(struct usb_device *usb, unsigned int ifnum,
                              const struct usb_device_id *id);
static void dbus_usbos_disconnect(struct usb_device *usb, void *ptr);
#endif /* KERNEL26 */

/**
 * have to disable missing-field-initializers warning as last element {}
 * triggers it and different versions of kernel have different number of members
 * so it is impossible to specify the initializer. BTW issuing the warning here
 * is bug og GCC as  universal zero {0} specified in C99 standard as correct way
 * of initialization of struct to all zeros
 */
#if defined(STRICT_GCC_WARNINGS) && defined(__GNUC__) &&                       \
    (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6))
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wmissing-field-initializers"
#endif

static struct usb_device_id devid_table[] = {
    {USB_DEVICE(BCM_DNGL_VID, 0x0000)}, /* Configurable via register() */
#if defined(BCM_REQUEST_FW)
    {USB_DEVICE(BCM_DNGL_VID, BCM_DNGL_BL_PID_4328)},
    {USB_DEVICE(BCM_DNGL_VID, BCM_DNGL_BL_PID_4322)},
    {USB_DEVICE(BCM_DNGL_VID, BCM_DNGL_BL_PID_4319)},
    {USB_DEVICE(BCM_DNGL_VID, BCM_DNGL_BL_PID_43236)},
    {USB_DEVICE(BCM_DNGL_VID, BCM_DNGL_BL_PID_43143)},
    {USB_DEVICE(BCM_DNGL_VID, BCM_DNGL_BL_PID_43242)},
    {USB_DEVICE(BCM_DNGL_VID, BCM_DNGL_BL_PID_4360)},
    {USB_DEVICE(BCM_DNGL_VID, BCM_DNGL_BL_PID_4350)},
    {USB_DEVICE(BCM_DNGL_VID, BCM_DNGL_BL_PID_43569)},
#endif
#ifdef EXTENDED_VID_PID
    EXTENDED_VID_PID,
#endif                                            /* EXTENDED_VID_PID */
    {USB_DEVICE(BCM_DNGL_VID, BCM_DNGL_BDC_PID)}, /* Default BDC */
    {}};

#if defined(STRICT_GCC_WARNINGS) && defined(__GNUC__) &&                       \
    (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6))
#pragma GCC diagnostic pop
#endif

MODULE_DEVICE_TABLE(usb, devid_table);

/** functions called by the Linux kernel USB subsystem */
static struct usb_driver dbus_usbdev = {
    name : "dbus_usbdev",
    probe : dbus_usbos_probe,
    disconnect : dbus_usbos_disconnect,
    id_table : devid_table,
#if defined(USB_SUSPEND_AVAILABLE)
    suspend : dbus_usbos_suspend,
    resume : dbus_usbos_resume,
    reset_resume : dbus_usbos_reset_resume,
    /* Linux USB core will allow autosuspend for devices bound to this driver */
    supports_autosuspend : 1
#endif /* USB_SUSPEND_AVAILABLE */
};

/**
 * This stores USB info during Linux probe callback since attach() is not called
 * yet at this point
 */
typedef struct {
    void *usbos_info;
    struct usb_device *usb; /* USB device pointer from OS */
    uint rx_pipe;           /* Pipe numbers for USB I/O */
    uint tx_pipe;           /* Pipe numbers for USB I/O */
    uint intr_pipe;         /* Pipe numbers for USB I/O */
    uint rx_pipe2;          /* Pipe numbers for USB I/O */
    int intr_size;          /* Size of interrupt message */
    int interval;           /* Interrupt polling interval */
    bool dldone;
    int vid;
    int pid;
    bool dereged;
    bool disc_cb_done;
    DEVICE_SPEED device_speed;
    enum usbos_suspend_state suspend_state;
    struct usb_interface *intf;
} probe_info_t;

/*
 * USB Linux dbus_intf_t
 */
static void *dbus_usbos_intf_attach(dbus_pub_t *pub, void *cbarg,
                                    dbus_intf_callbacks_t *cbs);
static void dbus_usbos_intf_detach(dbus_pub_t *pub, void *info);
static int dbus_usbos_intf_send_irb(void *bus, dbus_irb_tx_t *txirb);
static int dbus_usbos_intf_recv_irb(void *bus, dbus_irb_rx_t *rxirb);
static int dbus_usbos_intf_recv_irb_from_ep(void *bus, dbus_irb_rx_t *rxirb,
                                            uint32 ep_idx);
static int dbus_usbos_intf_cancel_irb(void *bus, dbus_irb_tx_t *txirb);
static int dbus_usbos_intf_send_ctl(void *bus, uint8 *buf, int len);
static int dbus_usbos_intf_recv_ctl(void *bus, uint8 *buf, int len);
static int dbus_usbos_intf_get_attrib(void *bus, dbus_attrib_t *attrib);
static int dbus_usbos_intf_up(void *bus);
static int dbus_usbos_intf_down(void *bus);
static int dbus_usbos_intf_stop(void *bus);
static int dbus_usbos_readreg(void *bus, uint32 regaddr, int datalen,
                              uint32 *value);
extern int dbus_usbos_loopback_tx(void *usbos_info_ptr, int cnt, int size);
int dbus_usbos_writereg(void *bus, uint32 regaddr, int datalen, uint32 data);
static int dbus_usbos_intf_set_config(void *bus, dbus_config_t *config);
static bool dbus_usbos_intf_recv_needed(void *bus);
static void *dbus_usbos_intf_exec_rxlock(void *bus, exec_cb_t cb,
                                         struct exec_parms *args);
static void *dbus_usbos_intf_exec_txlock(void *bus, exec_cb_t cb,
                                         struct exec_parms *args);
#ifdef BCMUSBDEV_COMPOSITE
static int dbus_usbos_intf_wlan(struct usb_device *usb);
#endif /* BCMUSBDEV_COMPOSITE */

/** functions called by dbus_usb.c */
static dbus_intf_t dbus_usbos_intf = {
    .attach = dbus_usbos_intf_attach,
    .detach = dbus_usbos_intf_detach,
    .up = dbus_usbos_intf_up,
    .down = dbus_usbos_intf_down,
    .send_irb = dbus_usbos_intf_send_irb,
    .recv_irb = dbus_usbos_intf_recv_irb,
    .cancel_irb = dbus_usbos_intf_cancel_irb,
    .send_ctl = dbus_usbos_intf_send_ctl,
    .recv_ctl = dbus_usbos_intf_recv_ctl,
    .get_stats = NULL,
    .get_attrib = dbus_usbos_intf_get_attrib,
    .remove = NULL,
    .resume = NULL,
    .suspend = NULL,
    .stop = dbus_usbos_intf_stop,
    .reset = NULL,
    .pktget = NULL,
    .pktfree = NULL,
    .iovar_op = NULL,
    .dump = NULL,
    .set_config = dbus_usbos_intf_set_config,
    .get_config = NULL,
    .device_exists = NULL,
    .dlneeded = NULL,
    .dlstart = NULL,
    .dlrun = NULL,
    .recv_needed = dbus_usbos_intf_recv_needed,
    .exec_rxlock = dbus_usbos_intf_exec_rxlock,
    .exec_txlock = dbus_usbos_intf_exec_txlock,

    .tx_timer_init = NULL,
    .tx_timer_start = NULL,
    .tx_timer_stop = NULL,

    .sched_dpc = NULL,
    .lock = NULL,
    .unlock = NULL,
    .sched_probe_cb = NULL,

    .shutdown = NULL,

    .recv_stop = NULL,
    .recv_resume = NULL,

    .recv_irb_from_ep = dbus_usbos_intf_recv_irb_from_ep,
    .readreg = dbus_usbos_readreg};

static probe_info_t g_probe_info;
static probe_cb_t probe_cb = NULL;
static disconnect_cb_t disconnect_cb = NULL;
static void *probe_arg = NULL;
static void *disc_arg = NULL;

static volatile int loopback_rx_cnt, loopback_tx_cnt;
int loopback_size;
bool is_loopback_pkt(void *buf);
int matches_loopback_pkt(void *buf);

/**
 * multiple code paths in this file dequeue a URB request, this function makes
 * sure that it happens in a concurrency save manner. Don't call this from a
 * sleepable process context.
 */
static urb_req_t *BCMFASTPATH dbus_usbos_qdeq(struct list_head *urbreq_q,
                                              spinlock_t *lock)
{
    unsigned long flags;
    urb_req_t *req;

    ASSERT(urbreq_q != NULL);

    spin_lock_irqsave(lock, flags);

    if (list_empty(urbreq_q)) {
        req = NULL;
    } else {
        ASSERT(urbreq_q->next != NULL);
        ASSERT(urbreq_q->next != urbreq_q);
#if defined(STRICT_GCC_WARNINGS) && defined(__GNUC__)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wcast-qual"
#endif
        req = list_entry(urbreq_q->next, urb_req_t, urb_list);
#if defined(STRICT_GCC_WARNINGS) && defined(__GNUC__)
#pragma GCC diagnostic pop
#endif
        list_del_init(&req->urb_list);
    }

    spin_unlock_irqrestore(lock, flags);

    return req;
}

static void BCMFASTPATH dbus_usbos_qenq(struct list_head *urbreq_q,
                                        urb_req_t *req, spinlock_t *lock)
{
    unsigned long flags;

    spin_lock_irqsave(lock, flags);

    list_add_tail(&req->urb_list, urbreq_q);

    spin_unlock_irqrestore(lock, flags);
}

/**
 * multiple code paths in this file remove a URB request from a list, this
 * function makes sure that it happens in a concurrency save manner. Don't call
 * this from a sleepable process context. Is quite similar to dbus_usbos_qdeq(),
 * I wonder why this function is needed.
 */
static void dbus_usbos_req_del(urb_req_t *req, spinlock_t *lock)
{
    unsigned long flags;

    spin_lock_irqsave(lock, flags);

    list_del_init(&req->urb_list);

    spin_unlock_irqrestore(lock, flags);
}

/**
 * Driver requires a pool of URBs to operate. This function is called during
 * initialization (attach phase), allocates a number of URBs, and puts them
 * on the free (req_rxfreeq and req_txfreeq) queue
 */
static int dbus_usbos_urbreqs_alloc(usbos_info_t *usbos_info, uint32 count,
                                    bool is_rx)
{
    int i;
    int allocated = 0;
    int err = DBUS_OK;

    for (i = 0; i < count; i++) {
        urb_req_t *req;

        req = MALLOC(usbos_info->pub->osh, sizeof(urb_req_t));
        if (req == NULL) {
            DBUSERR(("%s: MALLOC req failed\n", __FUNCTION__));
            err = DBUS_ERR_NOMEM;
            goto fail;
        }
        bzero(req, sizeof(urb_req_t));

        req->urb = USB_ALLOC_URB();
        if (req->urb == NULL) {
            DBUSERR(("%s: USB_ALLOC_URB req->urb failed\n", __FUNCTION__));
            err = DBUS_ERR_NOMEM;
            goto fail;
        }

        INIT_LIST_HEAD(&req->urb_list);

        if (is_rx) {
#if defined(BCM_RPC_NOCOPY) || defined(BCM_RPC_RXNOCOPY)
            /* don't allocate now. Do it on demand */
            req->pkt = NULL;
#else
            /* pre-allocate  buffers never to be released */
            req->pkt = MALLOC(usbos_info->pub->osh, usbos_info->rxbuf_len);
            if (req->pkt == NULL) {
                DBUSERR(("%s: MALLOC req->pkt failed\n", __FUNCTION__));
                err = DBUS_ERR_NOMEM;
                goto fail;
            }
#endif
            req->buf_len = usbos_info->rxbuf_len;
            dbus_usbos_qenq(&usbos_info->req_rxfreeq, req,
                            &usbos_info->rxfree_lock);
        } else {
            req->buf_len = 0;
            dbus_usbos_qenq(&usbos_info->req_txfreeq, req,
                            &usbos_info->txfree_lock);
        }
        allocated++;
        continue;

    fail:
        if (req) {
            if (is_rx && req->pkt) {
#if defined(BCM_RPC_NOCOPY) || defined(BCM_RPC_RXNOCOPY)
                /* req->pkt is NULL in "NOCOPY" mode */
#else
                MFREE(usbos_info->pub->osh, req->pkt, req->buf_len);
#endif
            }
            if (req->urb) {
                USB_FREE_URB(req->urb);
            }
            MFREE(usbos_info->pub->osh, req, sizeof(urb_req_t));
        }
        break;
    }

    atomic_add(allocated,
               is_rx ? &usbos_info->rxallocated : &usbos_info->txallocated);

    if (is_rx) {
        DBUSTRACE(("%s: add %d (total %d) rx buf, each has %d bytes\n",
                   __FUNCTION__, allocated,
                   atomic_read(&usbos_info->rxallocated),
                   usbos_info->rxbuf_len));
    } else {
        DBUSTRACE(("%s: add %d (total %d) tx req\n", __FUNCTION__, allocated,
                   atomic_read(&usbos_info->txallocated)));
    }

    return err;
} /* dbus_usbos_urbreqs_alloc */

/** Typically called during detach or when attach failed. Don't call until all
 * URBs unlinked */
static int dbus_usbos_urbreqs_free(usbos_info_t *usbos_info, bool is_rx)
{
    int rtn = 0;
    urb_req_t *req;
    struct list_head *req_q;
    spinlock_t *lock;

    if (is_rx) {
        req_q = &usbos_info->req_rxfreeq;
        lock = &usbos_info->rxfree_lock;
    } else {
        req_q = &usbos_info->req_txfreeq;
        lock = &usbos_info->txfree_lock;
    }
    while ((req = dbus_usbos_qdeq(req_q, lock)) != NULL) {
        if (is_rx) {
            if (req->pkt) {
                /* We do MFREE instead of PKTFREE because the pkt has been
                 * converted to native already
                 */
                MFREE(usbos_info->pub->osh, req->pkt, req->buf_len);
                req->pkt = NULL;
                req->buf_len = 0;
            }
        } else {
            /* sending req should not be assigned pkt buffer */
            ASSERT(req->pkt == NULL);
        }

        if (req->urb) {
            USB_FREE_URB(req->urb);
            req->urb = NULL;
        }
        MFREE(usbos_info->pub->osh, req, sizeof(urb_req_t));

        rtn++;
    }
    return rtn;
} /* dbus_usbos_urbreqs_free */

/**
 * called by Linux kernel on URB completion. Upper DBUS layer (dbus_usb.c) has
 * to be notified of send completion.
 */
void dbus_usbos_send_complete(CALLBACK_ARGS)
{
    urb_req_t *req = urb->context;
    dbus_irb_tx_t *txirb = req->arg;
    usbos_info_t *usbos_info = req->usbinfo;
    unsigned long flags;
    int status = DBUS_OK;
    int txposted;

    USB_AUTOPM_PUT_INTERFACE_ASYNC(g_probe_info.intf);

    spin_lock_irqsave(&usbos_info->txlock, flags);

    dbus_usbos_req_del(req, &usbos_info->txposted_lock);
    txposted = atomic_dec_return(&usbos_info->txposted);
    if (unlikely(txposted < 0)) {
        DBUSERR(("%s ERROR: txposted is negative (%d)!!\n", __FUNCTION__,
                 txposted));
    }
    spin_unlock_irqrestore(&usbos_info->txlock, flags);

    if (unlikely(urb->status)) {
        status = DBUS_ERR_TXFAIL;
        DBUSTRACE(("txfail status %d\n", urb->status));
    }

#if defined(BCM_RPC_NOCOPY) || defined(BCM_RPC_RXNOCOPY)
    /* sending req should not be assigned pkt buffer */
    ASSERT(req->pkt == NULL);
#endif
    /*  txirb should always be set, except for ZLP. ZLP is reusing this callback
     * function. */
    if (txirb != NULL) {
        if (txirb->send_buf != NULL) {
            MFREE(usbos_info->pub->osh, txirb->send_buf, req->buf_len);
            txirb->send_buf = NULL;
            req->buf_len = 0;
        }
        if (likely(usbos_info->cbarg && usbos_info->cbs)) {
            if (likely(usbos_info->cbs->send_irb_complete != NULL)) {
                usbos_info->cbs->send_irb_complete(usbos_info->cbarg, txirb,
                                                   status);
            }
        }
    }

    dbus_usbos_qenq(&usbos_info->req_txfreeq, req, &usbos_info->txfree_lock);
} /* dbus_usbos_send_complete */

/**
 * In order to receive USB traffic from the dongle, we need to supply the Linux
 * kernel with a free URB that is going to contain received data.
 */
static int BCMFASTPATH dbus_usbos_recv_urb_submit(usbos_info_t *usbos_info,
                                                  dbus_irb_rx_t *rxirb,
                                                  uint32 ep_idx)
{
    urb_req_t *req;
    int ret = DBUS_OK;
    unsigned long flags;
    void *p;
    uint rx_pipe;
    int rxposted;

    BCM_REFERENCE(rxposted);

    if (!(req = dbus_usbos_qdeq(&usbos_info->req_rxfreeq,
                                &usbos_info->rxfree_lock))) {
        DBUSTRACE(("%s No free URB!\n", __FUNCTION__));
        return DBUS_ERR_RXDROP;
    }

    spin_lock_irqsave(&usbos_info->rxlock, flags);

#if defined(BCM_RPC_NOCOPY) || defined(BCM_RPC_RXNOCOPY)
    req->pkt = rxirb->pkt = PKTGET(usbos_info->pub->osh, req->buf_len, FALSE);
    if (!rxirb->pkt) {
        DBUSERR(("%s: PKTGET failed\n", __FUNCTION__));
        dbus_usbos_qenq(&usbos_info->req_rxfreeq, req,
                        &usbos_info->rxfree_lock);
        ret = DBUS_ERR_RXDROP;
        goto fail;
    }
    /* consider the packet "native" so we don't count it as MALLOCED in the osl
     */
    PKTTONATIVE(usbos_info->pub->osh, req->pkt);
    rxirb->buf = NULL;
    p = PKTDATA(usbos_info->pub->osh, req->pkt);
#else
    if (req->buf_len != usbos_info->rxbuf_len) {
        ASSERT(req->pkt);
        MFREE(usbos_info->pub->osh, req->pkt, req->buf_len);
        DBUSTRACE(("%s: replace rx buff: old len %d, new len %d\n",
                   __FUNCTION__, req->buf_len, usbos_info->rxbuf_len));
        req->buf_len = 0;
        req->pkt = MALLOC(usbos_info->pub->osh, usbos_info->rxbuf_len);
        if (req->pkt == NULL) {
            DBUSERR(("%s: MALLOC req->pkt failed\n", __FUNCTION__));
            ret = DBUS_ERR_NOMEM;
            goto fail;
        }
        req->buf_len = usbos_info->rxbuf_len;
    }
    rxirb->buf = req->pkt;
    p = rxirb->buf;
#endif /* defined(BCM_RPC_NOCOPY) */
    rxirb->buf_len = req->buf_len;
    req->usbinfo = usbos_info;
    req->arg = rxirb;
    if (ep_idx == 0) {
        rx_pipe = usbos_info->rx_pipe;
    } else {
        rx_pipe = usbos_info->rx_pipe2;
        ASSERT(usbos_info->rx_pipe2);
    }
    /* Prepare the URB */
    usb_fill_bulk_urb(req->urb, usbos_info->usb, rx_pipe, p, rxirb->buf_len,
                      (usb_complete_t)dbus_usbos_recv_complete, req);
    req->urb->transfer_flags |= URB_QUEUE_BULK;

    if ((ret = USB_SUBMIT_URB(req->urb))) {
        DBUSERR(("%s USB_SUBMIT_URB failed. status %d\n", __FUNCTION__, ret));
        dbus_usbos_qenq(&usbos_info->req_rxfreeq, req,
                        &usbos_info->rxfree_lock);
        ret = DBUS_ERR_RXFAIL;
        goto fail;
    }
    rxposted = atomic_inc_return(&usbos_info->rxposted);

    dbus_usbos_qenq(&usbos_info->req_rxpostedq, req,
                    &usbos_info->rxposted_lock);
fail:
    spin_unlock_irqrestore(&usbos_info->rxlock, flags);
    return ret;
} /* dbus_usbos_recv_urb_submit */

/**
 * Called by worked thread when a 'receive URB' completed or Linux kernel when
 * it returns a URB to this driver.
 */
static void BCMFASTPATH dbus_usbos_recv_complete_handle(urb_req_t *req, int len,
                                                        int status)
{
    dbus_irb_rx_t *rxirb = req->arg;
    usbos_info_t *usbos_info = req->usbinfo;
    unsigned long flags;
    int rxallocated, rxposted;
    int dbus_status = DBUS_OK;
    bool killed =
        (g_probe_info.suspend_state == USBOS_SUSPEND_STATE_SUSPEND_PENDING) ? 1
                                                                            : 0;

    spin_lock_irqsave(&usbos_info->rxlock, flags);
    dbus_usbos_req_del(req, &usbos_info->rxposted_lock);
    rxposted = atomic_dec_return(&usbos_info->rxposted);
    rxallocated = atomic_read(&usbos_info->rxallocated);
    spin_unlock_irqrestore(&usbos_info->rxlock, flags);

    if ((rxallocated < usbos_info->pub->nrxq) && (!status) &&
        (rxposted == DBUS_USB_RXQUEUE_LOWER_WATERMARK)) {
        DBUSTRACE(("%s: need more rx buf: rxallocated %d rxposted %d!\n",
                   __FUNCTION__, rxallocated, rxposted));
        dbus_usbos_urbreqs_alloc(usbos_info,
                                 MIN(DBUS_USB_RXQUEUE_BATCH_ADD,
                                     usbos_info->pub->nrxq - rxallocated),
                                 TRUE);
    }

    /* Handle errors */
    if (status) {
        /*
         * Linux 2.4 disconnect: -ENOENT or -EILSEQ for CRC error; rmmod:
         * -ENOENT Linux 2.6 disconnect: -EPROTO, rmmod: -ESHUTDOWN
         */
        if ((status == -ENOENT && (!killed)) || status == -ESHUTDOWN) {
            /* NOTE: unlink() can not be called from URB callback().
             * Do not call dbusos_stop() here.
             */
            DBUSTRACE(("%s rx error %d\n", __FUNCTION__, status));
            dbus_usbos_state_change(usbos_info, DBUS_STATE_DOWN);
        } else if (status == -EPROTO) {
            DBUSTRACE(("%s rx error %d\n", __FUNCTION__, status));
        } else if (killed && (status == -EHOSTUNREACH || status == -ENOENT)) {
            /* Device is suspended */
        } else {
            DBUSTRACE(("%s rx error %d\n", __FUNCTION__, status));
            dbus_usbos_errhandler(usbos_info, DBUS_ERR_RXFAIL);
        }

        /* On error, don't submit more URBs yet */
        rxirb->buf = NULL;
        rxirb->actual_len = 0;
        dbus_status = DBUS_ERR_RXFAIL;
        goto fail;
    }

    /* Make the skb represent the received urb */
    rxirb->actual_len = len;

    if (rxirb->actual_len < sizeof(uint32)) {
        DBUSTRACE(("small pkt len %d, process as ZLP\n", rxirb->actual_len));
        dbus_status = DBUS_ERR_RXZLP;
    }

fail:
#if defined(BCM_RPC_NOCOPY) || defined(BCM_RPC_RXNOCOPY)
    /* detach the packet from the queue */
    req->pkt = NULL;
#endif /* BCM_RPC_NOCOPY || BCM_RPC_RXNOCOPY */

    if (usbos_info->cbarg && usbos_info->cbs) {
        if (usbos_info->cbs->recv_irb_complete) {
            usbos_info->cbs->recv_irb_complete(usbos_info->cbarg, rxirb,
                                               dbus_status);
        }
    }

    dbus_usbos_qenq(&usbos_info->req_rxfreeq, req, &usbos_info->rxfree_lock);

    /* Mark the interface as busy to reset USB autosuspend timer */
    USB_MARK_LAST_BUSY(usbos_info->usb);
} /* dbus_usbos_recv_complete_handle */

/** called by Linux kernel when it returns a URB to this driver */
static void dbus_usbos_recv_complete(CALLBACK_ARGS)
{
#ifdef USBOS_THREAD
    dbus_usbos_dispatch_schedule(CALLBACK_ARGS_DATA);
#else  /*  !USBOS_THREAD */
    dbus_usbos_recv_complete_handle(urb->context, urb->actual_length,
                                    urb->status);
#endif /*  USBOS_THREAD */
}

/**
 * If Linux notifies our driver that a control read or write URB has completed,
 * we should notify the DBUS layer above us (dbus_usb.c in this case).
 */
static void dbus_usbos_ctl_complete(usbos_info_t *usbos_info, int type,
                                    int urbstatus)
{
    int status = DBUS_ERR;

    if (usbos_info == NULL) {
        return;
    }

    switch (urbstatus) {
        case 0:
            status = DBUS_OK;
            break;
        case -EINPROGRESS:
        case -ENOENT:
        default:
#ifdef INTR_EP_ENABLE
            DBUSERR((
                "%s:%d fail status %d bus:%d susp:%d intr:%d ctli:%d ctlo:%d\n",
                __FUNCTION__, type, urbstatus, usbos_info->pub->busstate,
                g_probe_info.suspend_state, usbos_info->intr_urb_submitted,
                usbos_info->ctlin_urb_submitted,
                usbos_info->ctlout_urb_submitted));
#else
            DBUSERR(("%s: failed with status %d\n", __FUNCTION__, urbstatus));
            status = DBUS_ERR;
            break;
#endif /* INTR_EP_ENABLE */
    }

    if (usbos_info->cbarg && usbos_info->cbs) {
        if (usbos_info->cbs->ctl_complete) {
            usbos_info->cbs->ctl_complete(usbos_info->cbarg, type, status);
        }
    }
}

/** called by Linux */
static void dbus_usbos_ctlread_complete(CALLBACK_ARGS)
{
    usbos_info_t *usbos_info = (usbos_info_t *)urb->context;

    ASSERT(urb);
    usbos_info = (usbos_info_t *)urb->context;

    dbus_usbos_ctl_complete(usbos_info, DBUS_CBCTL_READ, urb->status);

#ifdef USBOS_THREAD
    if (usbos_info->rxctl_deferrespok) {
        usbos_info->ctl_read.bRequestType =
            USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE;
        usbos_info->ctl_read.bRequest = 1;
    }
#endif

    up(&usbos_info->ctl_lock);

    USB_AUTOPM_PUT_INTERFACE_ASYNC(g_probe_info.intf);
}

/** called by Linux */
static void dbus_usbos_ctlwrite_complete(CALLBACK_ARGS)
{
    usbos_info_t *usbos_info = (usbos_info_t *)urb->context;

    ASSERT(urb);
    usbos_info = (usbos_info_t *)urb->context;

    dbus_usbos_ctl_complete(usbos_info, DBUS_CBCTL_WRITE, urb->status);

#ifdef USBOS_TX_THREAD
    usbos_info->ctl_state = USBOS_REQUEST_STATE_UNSCHEDULED;
#endif /* USBOS_TX_THREAD */

    up(&usbos_info->ctl_lock);

    USB_AUTOPM_PUT_INTERFACE_ASYNC(g_probe_info.intf);
}

#ifdef INTR_EP_ENABLE
/** called by Linux */
static void dbus_usbos_intr_complete(CALLBACK_ARGS)
{
    usbos_info_t *usbos_info = (usbos_info_t *)urb->context;
    bool killed =
        (g_probe_info.suspend_state == USBOS_SUSPEND_STATE_SUSPEND_PENDING) ? 1
                                                                            : 0;

    if (usbos_info == NULL || usbos_info->pub == NULL) {
        return;
    }
    if ((urb->status == -ENOENT && (!killed)) || urb->status == -ESHUTDOWN ||
        urb->status == -ENODEV) {
        dbus_usbos_state_change(usbos_info, DBUS_STATE_DOWN);
    }

    if (usbos_info->pub->busstate == DBUS_STATE_DOWN) {
        DBUSERR(("%s: intr cb when DBUS down, ignoring\n", __FUNCTION__));
        return;
    }
    dbus_usbos_ctl_complete(usbos_info, DBUS_CBINTR_POLL, urb->status);
}
#endif /* INTR_EP_ENABLE */

/**
 * when the bus is going to sleep or halt, the Linux kernel requires us to take
 * ownership of our URBs again. Multiple code paths in this file require a list
 * of URBs to be cancelled in a concurrency save manner.
 */
static void dbus_usbos_unlink(struct list_head *urbreq_q, spinlock_t *lock)
{
    urb_req_t *req;

    /* dbus_usbos_recv_complete() adds req back to req_freeq */
    while ((req = dbus_usbos_qdeq(urbreq_q, lock)) != NULL) {
        ASSERT(req->urb != NULL);
        USB_UNLINK_URB(req->urb);
    }
}

/** multiple code paths in this file require the bus to stop */
static void dbus_usbos_cancel_all_urbs(usbos_info_t *usbos_info)
{
    int rxposted, txposted;

    DBUSTRACE(("%s: unlink all URBs\n", __FUNCTION__));

#ifdef USBOS_TX_THREAD
    usbos_info->ctl_state = USBOS_REQUEST_STATE_UNSCHEDULED;

    /* Yield the CPU to TX thread so all pending requests are submitted */
    while (!list_empty(&usbos_info->usbos_tx_list)) {
        wake_up_interruptible(&usbos_info->usbos_tx_queue_head);
        OSL_SLEEP(0xA);
    }
#endif /* USBOS_TX_THREAD */

    /* tell Linux kernel to cancel a single intr, ctl and blk URB */
    if (usbos_info->intr_urb) {
        USB_UNLINK_URB(usbos_info->intr_urb);
    }
    if (usbos_info->ctl_urb) {
        USB_UNLINK_URB(usbos_info->ctl_urb);
    }
    if (usbos_info->blk_urb) {
        USB_UNLINK_URB(usbos_info->blk_urb);
    }

    dbus_usbos_unlink(&usbos_info->req_txpostedq, &usbos_info->txposted_lock);
    dbus_usbos_unlink(&usbos_info->req_rxpostedq, &usbos_info->rxposted_lock);

    /* Wait until the callbacks for all submitted URBs have been called, because
     * the handler needs to know is an USB suspend is in progress.
     */
    SPINWAIT((atomic_read(&usbos_info->txposted) != 0 ||
              atomic_read(&usbos_info->rxposted) != 0),
             0x2710);

    txposted = atomic_read(&usbos_info->txposted);
    rxposted = atomic_read(&usbos_info->rxposted);
    if (txposted != 0 || rxposted != 0) {
        DBUSERR(("%s ERROR: REQs posted, rx=%d tx=%d!\n", __FUNCTION__,
                 rxposted, txposted));
    }
} /* dbus_usbos_cancel_all_urbs */

/** multiple code paths require the bus to stop */
static void dbusos_stop(usbos_info_t *usbos_info)
{
    urb_req_t *req;
    int rxposted;
    req = NULL;
    BCM_REFERENCE(req);

    ASSERT(usbos_info);

    dbus_usbos_state_change(usbos_info, DBUS_STATE_DOWN);

    dbus_usbos_cancel_all_urbs(usbos_info);

#ifdef USBOS_THREAD
    /* yield the CPU to rx packet thread */
    while (1) {
        if (atomic_read(&usbos_info->usbos_list_cnt) <= 0) {
            break;
        }
        wake_up_interruptible(&usbos_info->usbos_queue_head);
        OSL_SLEEP(0x3);
    }
#endif /* USBOS_THREAD */

    rxposted = atomic_read(&usbos_info->rxposted);
    if (rxposted > 0) {
        DBUSERR(
            ("%s ERROR: rx REQs posted=%d in stop!\n", __FUNCTION__, rxposted));
    }

    ASSERT(atomic_read(&usbos_info->txposted) == 0 && rxposted == 0);
} /* dbusos_stop */

#if defined(USB_SUSPEND_AVAILABLE)

/**
 * Linux kernel sports a 'USB auto suspend' feature. See:
 * http://lwn.net/Articles/373550/ The suspend method is called by the Linux
 * kernel to warn the driver that the device is going to be suspended.  If the
 * driver returns a negative error code, the suspend will be aborted. If the
 * driver returns 0, it must cancel all outstanding URBs (usb_kill_urb()) and
 * not submit any more.
 */
static int dbus_usbos_suspend(struct usb_interface *intf, pm_message_t message)
{
    DBUSERR(
        ("%s suspend state: %d\n", __FUNCTION__, g_probe_info.suspend_state));
    /* DHD for full dongle model */
    g_probe_info.suspend_state = USBOS_SUSPEND_STATE_SUSPEND_PENDING;
    dbus_usbos_state_change((usbos_info_t *)g_probe_info.usbos_info,
                            DBUS_STATE_SLEEP);
    dbus_usbos_cancel_all_urbs((usbos_info_t *)g_probe_info.usbos_info);
    g_probe_info.suspend_state = USBOS_SUSPEND_STATE_SUSPENDED;

    return 0;
}

/**
 * The resume method is called to tell the driver that the device has been
 * resumed and the driver can return to normal operation.  URBs may once more be
 * submitted.
 */
static int dbus_usbos_resume(struct usb_interface *intf)
{
    DBUSERR(("%s Device resumed\n", __FUNCTION__));

    dbus_usbos_state_change((usbos_info_t *)g_probe_info.usbos_info,
                            DBUS_STATE_UP);
    g_probe_info.suspend_state = USBOS_SUSPEND_STATE_DEVICE_ACTIVE;
    return 0;
}

/**
 * This function is directly called by the Linux kernel, when the suspended
 * device has been reset instead of being resumed
 */
static int dbus_usbos_reset_resume(struct usb_interface *intf)
{
    DBUSERR(("%s Device reset resumed\n", __FUNCTION__));

    /* The device may have lost power, so a firmware download may be required */
    dbus_usbos_state_change((usbos_info_t *)g_probe_info.usbos_info,
                            DBUS_STATE_DL_NEEDED);
    g_probe_info.suspend_state = USBOS_SUSPEND_STATE_DEVICE_ACTIVE;
    return 0;
}

#endif /* USB_SUSPEND_AVAILABLE */

/**
 * Called by Linux kernel at initialization time, kernel wants to know if our
 * driver will accept the caller supplied USB interface. Note that USB drivers
 * are bound to interfaces, and not to USB devices.
 */
#ifdef KERNEL26
#define DBUS_USBOS_PROBE()                                                     \
    static int dbus_usbos_probe(struct usb_interface *intf,                    \
                                const struct usb_device_id *id)
#define DBUS_USBOS_DISCONNECT()                                                \
    static void dbus_usbos_disconnect(struct usb_interface *intf)
#else
#define DBUS_USBOS_PROBE()                                                     \
    static void *dbus_usbos_probe(struct usb_device *usb, unsigned int ifnum,  \
                                  const struct usb_device_id *id)
#define DBUS_USBOS_DISCONNECT()                                                \
    static void dbus_usbos_disconnect(struct usb_device *usb, void *ptr)
#endif /* KERNEL26 */

DBUS_USBOS_PROBE()
{
    int ep;
    struct usb_endpoint_descriptor *endpoint;
    int ret = 0;
#ifdef KERNEL26
    struct usb_device *usb = interface_to_usbdev(intf);
#else
    int claimed = 0;
#endif
    int num_of_eps;
#ifdef BCMUSBDEV_COMPOSITE
    int wlan_if = -1;
    bool intr_ep = FALSE;
#endif /* BCMUSBDEV_COMPOSITE */
    wifi_adapter_info_t *adapter;

    DHD_MUTEX_LOCK();

    DBUSERR(("%s: bus num(busnum)=%d, slot num (portnum)=%d\n", __FUNCTION__,
             usb->bus->busnum, usb->portnum));
    adapter = dhd_wifi_platform_attach_adapter(
        USB_BUS, usb->bus->busnum, usb->portnum, WIFI_STATUS_POWER_ON);
    if (adapter == NULL) {
        DBUSERR(("%s: can't find adapter info for this chip\n", __FUNCTION__));
        goto fail;
    }

#ifdef BCMUSBDEV_COMPOSITE
    wlan_if = dbus_usbos_intf_wlan(usb);
#ifdef KERNEL26
    if ((wlan_if >= 0) && (IFPTR(usb, wlan_if) == intf))
#else
    if (wlan_if == ifnum)
#endif /* KERNEL26 */
    {
#endif /* BCMUSBDEV_COMPOSITE */
        g_probe_info.usb = usb;
        g_probe_info.dldone = TRUE;
#ifdef BCMUSBDEV_COMPOSITE
    } else {
        DBUSTRACE(("dbus_usbos_probe: skip probe for non WLAN interface\n"));
        ret = BCME_UNSUPPORTED;
        goto fail;
    }
#endif /* BCMUSBDEV_COMPOSITE */

#ifdef KERNEL26
    g_probe_info.intf = intf;
#endif /* KERNEL26 */

#ifdef BCMUSBDEV_COMPOSITE
    if (IFDESC(usb, wlan_if).bInterfaceNumber > USB_COMPIF_MAX)
#else
    if (IFDESC(usb, CONTROL_IF).bInterfaceNumber)
#endif /* BCMUSBDEV_COMPOSITE */
    {
        ret = -1;
        goto fail;
    }
    if (id != NULL) {
        g_probe_info.vid = id->idVendor;
        g_probe_info.pid = id->idProduct;
    }

#ifdef KERNEL26
    usb_set_intfdata(intf, &g_probe_info);
#endif

    /* Check that the device supports only one configuration */
    if (usb->descriptor.bNumConfigurations != 1) {
        ret = -1;
        goto fail;
    }

    if (usb->descriptor.bDeviceClass != USB_CLASS_VENDOR_SPEC) {
#ifdef BCMUSBDEV_COMPOSITE
        if ((usb->descriptor.bDeviceClass != USB_CLASS_MISC) &&
            (usb->descriptor.bDeviceClass != USB_CLASS_WIRELESS)) {
#endif /* BCMUSBDEV_COMPOSITE */
            ret = -1;
            goto fail;
#ifdef BCMUSBDEV_COMPOSITE
        }
#endif /* BCMUSBDEV_COMPOSITE */
    }

    /*
     * Only the BDC interface configuration is supported:
     *	Device class: USB_CLASS_VENDOR_SPEC
     *	if0 class: USB_CLASS_VENDOR_SPEC
     *	if0/ep0: control
     *	if0/ep1: bulk in
     *	if0/ep2: bulk out (ok if swapped with bulk in)
     */
    if (CONFIGDESC(usb)->bNumInterfaces != 1) {
#ifdef BCMUSBDEV_COMPOSITE
        if (CONFIGDESC(usb)->bNumInterfaces > USB_COMPIF_MAX) {
#endif /* BCMUSBDEV_COMPOSITE */
            ret = -1;
            goto fail;
#ifdef BCMUSBDEV_COMPOSITE
        }
#endif /* BCMUSBDEV_COMPOSITE */
    }

    /* Check interface */
#ifndef KERNEL26
#ifdef BCMUSBDEV_COMPOSITE
    if (usb_interface_claimed(IFPTR(usb, wlan_if)))
#else
    if (usb_interface_claimed(IFPTR(usb, CONTROL_IF)))
#endif /* BCMUSBDEV_COMPOSITE */
    {
        ret = -1;
        goto fail;
    }
#endif /* !KERNEL26 */

#ifdef BCMUSBDEV_COMPOSITE
    if ((IFDESC(usb, wlan_if).bInterfaceClass != USB_CLASS_VENDOR_SPEC ||
         IFDESC(usb, wlan_if).bInterfaceSubClass != 0x2 ||
         IFDESC(usb, wlan_if).bInterfaceProtocol != 0xff) &&
        (IFDESC(usb, wlan_if).bInterfaceClass != USB_CLASS_MISC ||
         IFDESC(usb, wlan_if).bInterfaceSubClass != USB_SUBCLASS_COMMON ||
         IFDESC(usb, wlan_if).bInterfaceProtocol != USB_PROTO_IAD))
#else
    if (IFDESC(usb, CONTROL_IF).bInterfaceClass != USB_CLASS_VENDOR_SPEC ||
        IFDESC(usb, CONTROL_IF).bInterfaceSubClass != 0x2 ||
        IFDESC(usb, CONTROL_IF).bInterfaceProtocol != 0xff)
#endif /* BCMUSBDEV_COMPOSITE */
    {
#ifdef BCMUSBDEV_COMPOSITE
        DBUSERR(
            ("%s: invalid control interface: class %d, subclass %d, proto %d\n",
             __FUNCTION__, IFDESC(usb, wlan_if).bInterfaceClass,
             IFDESC(usb, wlan_if).bInterfaceSubClass,
             IFDESC(usb, wlan_if).bInterfaceProtocol));
#else
        DBUSERR(
            ("%s: invalid control interface: class %d, subclass %d, proto %d\n",
             __FUNCTION__, IFDESC(usb, CONTROL_IF).bInterfaceClass,
             IFDESC(usb, CONTROL_IF).bInterfaceSubClass,
             IFDESC(usb, CONTROL_IF).bInterfaceProtocol));
#endif /* BCMUSBDEV_COMPOSITE */
        ret = -1;
        goto fail;
    }

    /* Check control endpoint */
#ifdef BCMUSBDEV_COMPOSITE
    endpoint = &IFEPDESC(usb, wlan_if, 0);
#else
    endpoint = &IFEPDESC(usb, CONTROL_IF, 0);
#endif /* BCMUSBDEV_COMPOSITE */
    if ((endpoint->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) !=
        USB_ENDPOINT_XFER_INT) {
#ifdef BCMUSBDEV_COMPOSITE
        if ((endpoint->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) !=
            USB_ENDPOINT_XFER_BULK) {
#endif /* BCMUSBDEV_COMPOSITE */
            DBUSERR(("%s: invalid control endpoint %d\n", __FUNCTION__,
                     endpoint->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK));
            ret = -1;
            goto fail;
#ifdef BCMUSBDEV_COMPOSITE
        }
#endif /* BCMUSBDEV_COMPOSITE */
    }

#ifdef BCMUSBDEV_COMPOSITE
    if ((endpoint->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) ==
        USB_ENDPOINT_XFER_INT) {
#endif /* BCMUSBDEV_COMPOSITE */
        g_probe_info.intr_pipe = usb_rcvintpipe(
            usb, endpoint->bEndpointAddress & USB_ENDPOINT_NUMBER_MASK);
#ifdef BCMUSBDEV_COMPOSITE
        intr_ep = TRUE;
    }
#endif /* BCMUSBDEV_COMPOSITE */

#ifndef KERNEL26
    /* Claim interface */
#ifdef BCMUSBDEV_COMPOSITE
    usb_driver_claim_interface(&dbus_usbdev, IFPTR(usb, wlan_if),
                               &g_probe_info);
#else
    usb_driver_claim_interface(&dbus_usbdev, IFPTR(usb, CONTROL_IF),
                               &g_probe_info);
#endif /* BCMUSBDEV_COMPOSITE */
    claimed = 1;
#endif /* !KERNEL26 */
    g_probe_info.rx_pipe = 0;
    g_probe_info.rx_pipe2 = 0;
    g_probe_info.tx_pipe = 0;
#ifdef BCMUSBDEV_COMPOSITE
    if (intr_ep) {
        ep = 1;
    } else {
        ep = 0;
    }
    num_of_eps = IFDESC(usb, wlan_if).bNumEndpoints - 1;
#else
    num_of_eps = IFDESC(usb, BULK_IF).bNumEndpoints - 1;
#endif /* BCMUSBDEV_COMPOSITE */

    if ((num_of_eps != 0x2) && (num_of_eps != 0x3)) {
#ifdef BCMUSBDEV_COMPOSITE
        if (num_of_eps > 0x7)
#endif /* BCMUSBDEV_COMPOSITE */
            ASSERT(0);
    }
    /* Check data endpoints and get pipes */
#ifdef BCMUSBDEV_COMPOSITE
    for (; ep <= num_of_eps; ep++)
#else
    for (ep = 1; ep <= num_of_eps; ep++)
#endif /* BCMUSBDEV_COMPOSITE */
    {
#ifdef BCMUSBDEV_COMPOSITE
        endpoint = &IFEPDESC(usb, wlan_if, ep);
#else
        endpoint = &IFEPDESC(usb, BULK_IF, ep);
#endif /* BCMUSBDEV_COMPOSITE */
        if ((endpoint->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) !=
            USB_ENDPOINT_XFER_BULK) {
            DBUSERR(("%s: invalid data endpoint %d\n", __FUNCTION__, ep));
            ret = -1;
            goto fail;
        }

        if ((endpoint->bEndpointAddress & USB_ENDPOINT_DIR_MASK) ==
            USB_DIR_IN) {
            /* direction: dongle->host */
            if (!g_probe_info.rx_pipe) {
                g_probe_info.rx_pipe =
                    usb_rcvbulkpipe(usb, (endpoint->bEndpointAddress &
                                          USB_ENDPOINT_NUMBER_MASK));
            } else {
                g_probe_info.rx_pipe2 =
                    usb_rcvbulkpipe(usb, (endpoint->bEndpointAddress &
                                          USB_ENDPOINT_NUMBER_MASK));
            }
        } else {
            g_probe_info.tx_pipe = usb_sndbulkpipe(
                usb, (endpoint->bEndpointAddress & USB_ENDPOINT_NUMBER_MASK));
        }
    }

    /* Allocate interrupt URB and data buffer */
    /* RNDIS says 8-byte intr, our old drivers used 4-byte */
#ifdef BCMUSBDEV_COMPOSITE
    g_probe_info.intr_size =
        (IFEPDESC(usb, wlan_if, 0).wMaxPacketSize == 0x10) ? 0x8 : 0x4;
    g_probe_info.interval = IFEPDESC(usb, wlan_if, 0).bInterval;
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 21))
    usb->quirks |= USB_QUIRK_NO_SET_INTF;
#endif
#else
    g_probe_info.intr_size =
        (IFEPDESC(usb, CONTROL_IF, 0).wMaxPacketSize == 0x10) ? 0x8 : 0x4;
    g_probe_info.interval = IFEPDESC(usb, CONTROL_IF, 0).bInterval;
#endif /* BCMUSBDEV_COMPOSITE */

#ifndef KERNEL26
    /* usb_fill_int_urb does the interval decoding in 2.6 */
    if (usb->speed == USB_SPEED_HIGH) {
        g_probe_info.interval = 1 << (g_probe_info.interval - 1);
    }
#endif
    if (usb->speed == USB_SPEED_SUPER) {
        g_probe_info.device_speed = SUPER_SPEED;
        DBUSERR(("super speed device detected\n"));
    } else if (usb->speed == USB_SPEED_HIGH) {
        g_probe_info.device_speed = HIGH_SPEED;
        DBUSERR(("high speed device detected\n"));
    } else {
        g_probe_info.device_speed = FULL_SPEED;
        DBUSERR(("full speed device detected\n"));
    }
    if (g_probe_info.dereged == FALSE && probe_cb) {
        disc_arg =
            probe_cb(probe_arg, "", USB_BUS, usb->bus->busnum, usb->portnum, 0);
    }

    g_probe_info.disc_cb_done = FALSE;

#ifdef KERNEL26
    intf->needs_remote_wakeup = 1;
#endif /* KERNEL26 */
    DHD_MUTEX_UNLOCK();

    /* Success */
#ifdef KERNEL26
    return DBUS_OK;
#else
    usb_inc_dev_use(usb);
    return &g_probe_info;
#endif

fail:
    printf("%s: Exit ret=%d\n", __FUNCTION__, ret);
#ifdef BCMUSBDEV_COMPOSITE
    if (ret != BCME_UNSUPPORTED)
#endif /* BCMUSBDEV_COMPOSITE */
        DBUSERR(("%s: failed with errno %d\n", __FUNCTION__, ret));
#ifndef KERNEL26
    if (claimed)
#ifdef BCMUSBDEV_COMPOSITE
        usb_driver_release_interface(&dbus_usbdev, IFPTR(usb, wlan_if));
#else
        usb_driver_release_interface(&dbus_usbdev, IFPTR(usb, CONTROL_IF));
#endif /* BCMUSBDEV_COMPOSITE */
#endif /* !KERNEL26 */

    DHD_MUTEX_UNLOCK();
#ifdef KERNEL26
    usb_set_intfdata(intf, NULL);
    return ret;
#else
    return NULL;
#endif
} /* dbus_usbos_probe */

/** Called by Linux kernel, is the counter part of dbus_usbos_probe() */
DBUS_USBOS_DISCONNECT()
{
#ifdef KERNEL26
    struct usb_device *usb = interface_to_usbdev(intf);
    probe_info_t *probe_usb_init_data = usb_get_intfdata(intf);
#else
    probe_info_t *probe_usb_init_data = (probe_info_t *)ptr;
#endif
    usbos_info_t *usbos_info;

    DHD_MUTEX_LOCK();

    DBUSERR(("%s: bus num(busnum)=%d, slot num (portnum)=%d\n", __FUNCTION__,
             usb->bus->busnum, usb->portnum));

    if (probe_usb_init_data) {
        usbos_info = (usbos_info_t *)probe_usb_init_data->usbos_info;
        if (usbos_info) {
            if ((probe_usb_init_data->dereged == FALSE) && disconnect_cb &&
                disc_arg) {
                disconnect_cb(disc_arg);
                disc_arg = NULL;
                probe_usb_init_data->disc_cb_done = TRUE;
            }
        }
    }

    if (usb) {
#ifndef KERNEL26
#ifdef BCMUSBDEV_COMPOSITE
        usb_driver_release_interface(&dbus_usbdev, IFPTR(usb, wlan_if));
#else
        usb_driver_release_interface(&dbus_usbdev, IFPTR(usb, CONTROL_IF));
#endif /* BCMUSBDEV_COMPOSITE */
        usb_dec_dev_use(usb);
#endif /* !KERNEL26 */
    }
    DHD_MUTEX_UNLOCK();
} /* dbus_usbos_disconnect */

#define LOOPBACK_PKT_START 0xBABE1234

bool is_loopback_pkt(void *buf)
{
    uint32 *buf_ptr = (uint32 *)buf;

    if (*buf_ptr == LOOPBACK_PKT_START) {
        return TRUE;
    }
    return FALSE;
}

int matches_loopback_pkt(void *buf)
{
    int i, j;
    unsigned char *cbuf = (unsigned char *)buf;

    for (i = 0x4; i < loopback_size; i++) {
        if (cbuf[i] != (i % 0x100)) {
            printf("%s: mismatch at i=%d %d : ", __FUNCTION__, i, cbuf[i]);
            for (j = i; ((j < i + 0x10) && (j < loopback_size)); j++) {
                printf("%d ", cbuf[j]);
            }
            printf("\n");
            return 0;
        }
    }
    loopback_rx_cnt++;
    return 1;
}

int dbus_usbos_loopback_tx(void *usbos_info_ptr, int cnt, int size)
{
    usbos_info_t *usbos_info = (usbos_info_t *)usbos_info_ptr;
    unsigned char *buf;
    int j;
    void *p = NULL;
    int rc, last_rx_cnt;
    int tx_failed_cnt;
    int max_size = 1650;
    int usb_packet_size = 512;
    int min_packet_size = 10;

    if (size % usb_packet_size == 0) {
        size = size - 1;
        DBUSERR(("%s: overriding size=%d \n", __FUNCTION__, size));
    }

    if (size < min_packet_size) {
        size = min_packet_size;
        DBUSERR(("%s: overriding size=%d\n", __FUNCTION__, min_packet_size));
    }
    if (size > max_size) {
        size = max_size;
        DBUSERR(("%s: overriding size=%d\n", __FUNCTION__, max_size));
    }

    loopback_tx_cnt = 0;
    loopback_rx_cnt = 0;
    tx_failed_cnt = 0;
    loopback_size = size;

    while (loopback_tx_cnt < cnt) {
        uint32 *x;
        int pkt_size = loopback_size;

        p = PKTGET(usbos_info->pub->osh, pkt_size, TRUE);
        if (p == NULL) {
            DBUSERR(("%s:%d Failed to allocate packet sz=%d\n", __FUNCTION__,
                     __LINE__, pkt_size));
            return BCME_ERROR;
        }
        x = (uint32 *)PKTDATA(usbos_info->pub->osh, p);
        *x = LOOPBACK_PKT_START;
        buf = (unsigned char *)x;
        for (j = 0x4; j < pkt_size; j++) {
            buf[j] = j % 0x100;
        }
        rc = dbus_send_buf(usbos_info->pub, buf, pkt_size, p);
        if (rc != BCME_OK) {
            DBUSERR(("%s:%d Freeing packet \n", __FUNCTION__, __LINE__));
            PKTFREE(usbos_info->pub->osh, p, TRUE);
            dbus_usbos_wait(usbos_info, 1);
            tx_failed_cnt++;
        } else {
            loopback_tx_cnt++;
            tx_failed_cnt = 0;
        }
        if (tx_failed_cnt == 0x5) {
            DBUSERR(("%s : Failed to send loopback packets cnt=%d "
                     "loopback_tx_cnt=%d\n",
                     __FUNCTION__, cnt, loopback_tx_cnt));
            break;
        }
    }
    printf("Transmitted %d loopback packets of size %d\n", loopback_tx_cnt,
           loopback_size);

    last_rx_cnt = loopback_rx_cnt;
    while (loopback_rx_cnt < loopback_tx_cnt) {
        dbus_usbos_wait(usbos_info, 1);
        if (loopback_rx_cnt <= last_rx_cnt) {
            DBUSERR(("%s: Matched rx cnt stuck at %d \n", __FUNCTION__,
                     last_rx_cnt));
            return BCME_ERROR;
        }
        last_rx_cnt = loopback_rx_cnt;
    }
    printf("Received %d loopback packets of size %d\n", loopback_tx_cnt,
           loopback_size);

    return BCME_OK;
} /* dbus_usbos_loopback_tx */

/**
 * Higher layer (dbus_usb.c) wants to transmit an I/O Request Block
 *     @param[in] txirb txirb->pkt, if non-zero, contains a single or a chain of
 * packets
 */
static int dbus_usbos_intf_send_irb(void *bus, dbus_irb_tx_t *txirb)
{
    usbos_info_t *usbos_info = (usbos_info_t *)bus;
    urb_req_t *req, *req_zlp = NULL;
    int ret = DBUS_OK;
    unsigned long flags;
    void *pkt;
    uint32 buffer_length;
    uint8 *buf;

    if ((usbos_info == NULL) || !usbos_info->tx_pipe) {
        return DBUS_ERR;
    }

    if (txirb->pkt != NULL) {
        buffer_length = pkttotlen(usbos_info->pub->osh, txirb->pkt);
        /* In case of multiple packets the values below may be overwritten */
        txirb->send_buf = NULL;
        buf = PKTDATA(usbos_info->pub->osh, txirb->pkt);
    } else { /* txirb->buf != NULL */
        ASSERT(txirb->buf != NULL);
        ASSERT(txirb->send_buf == NULL);
        buffer_length = txirb->len;
        buf = txirb->buf;
    }

    if (!(req = dbus_usbos_qdeq(&usbos_info->req_txfreeq,
                                &usbos_info->txfree_lock))) {
        DBUSERR(("%s No free URB!\n", __FUNCTION__));
        return DBUS_ERR_TXDROP;
    }

    /* If not using standard Linux kernel functionality for handling Zero Length
     * Packet(ZLP), the dbus needs to generate ZLP when length is multiple of
     * MaxPacketSize.
     */
#ifndef WL_URB_ZPKT
    if (!(buffer_length % usbos_info->maxps)) {
        if (!(req_zlp = dbus_usbos_qdeq(&usbos_info->req_txfreeq,
                                        &usbos_info->txfree_lock))) {
            DBUSERR(("%s No free URB for ZLP!\n", __FUNCTION__));
            dbus_usbos_qenq(&usbos_info->req_txfreeq, req,
                            &usbos_info->txfree_lock);
            return DBUS_ERR_TXDROP;
        }

        /* No txirb, so that dbus_usbos_send_complete can differentiate between
         * DATA and ZLP.
         */
        req_zlp->arg = NULL;
        req_zlp->usbinfo = usbos_info;
        req_zlp->buf_len = 0;

        usb_fill_bulk_urb(req_zlp->urb, usbos_info->usb, usbos_info->tx_pipe,
                          NULL, 0, (usb_complete_t)dbus_usbos_send_complete,
                          req_zlp);

        req_zlp->urb->transfer_flags |= URB_QUEUE_BULK;
    }
#endif /* !WL_URB_ZPKT */

#ifndef USBOS_TX_THREAD
    /* Disable USB autosuspend until this request completes, request USB resume
     * if needed. Because this call runs asynchronously, there is no guarantee
     * the bus is resumed before the URB is submitted, and the URB might be
     * dropped. Use USBOS_TX_THREAD to avoid this.
     */
    USB_AUTOPM_GET_INTERFACE_ASYNC(g_probe_info.intf);
#endif /* !USBOS_TX_THREAD */

    spin_lock_irqsave(&usbos_info->txlock, flags);

    req->arg = txirb;
    req->usbinfo = usbos_info;
    req->buf_len = 0;

    /* Prepare the URB */
    if (txirb->pkt != NULL) {
        uint32 pktlen;
        uint8 *transfer_buf;

        /* For multiple packets, allocate contiguous buffer and copy packet data
         * to it */
        if (PKTNEXT(usbos_info->pub->osh, txirb->pkt)) {
            transfer_buf = MALLOC(usbos_info->pub->osh, buffer_length);
            if (!transfer_buf) {
                ret = DBUS_ERR_TXDROP;
                DBUSERR(("fail to alloc to usb buffer\n"));
                goto fail;
            }

            pkt = txirb->pkt;
            txirb->send_buf = transfer_buf;
            req->buf_len = buffer_length;

            while (pkt) {
                pktlen = PKTLEN(usbos_info->pub->osh, pkt);
                bcopy(PKTDATA(usbos_info->pub->osh, pkt), transfer_buf, pktlen);
                transfer_buf += pktlen;
                pkt = PKTNEXT(usbos_info->pub->osh, pkt);
            }

            ASSERT(((uint8 *)txirb->send_buf + buffer_length) == transfer_buf);

            /* Overwrite buf pointer with pointer to allocated contiguous
             * transfer_buf
             */
            buf = txirb->send_buf;
        }
    }

    usb_fill_bulk_urb(req->urb, usbos_info->usb, usbos_info->tx_pipe, buf,
                      buffer_length, (usb_complete_t)dbus_usbos_send_complete,
                      req);

    req->urb->transfer_flags |= URB_QUEUE_BULK;

#ifdef USBOS_TX_THREAD
    /* Enqueue TX request, the TX thread will resume the bus if needed and
     * submit it asynchronously
     */
    dbus_usbos_qenq(&usbos_info->usbos_tx_list, req,
                    &usbos_info->usbos_tx_list_lock);
    if (req_zlp != NULL) {
        dbus_usbos_qenq(&usbos_info->usbos_tx_list, req_zlp,
                        &usbos_info->usbos_tx_list_lock);
    }
    spin_unlock_irqrestore(&usbos_info->txlock, flags);

    wake_up_interruptible(&usbos_info->usbos_tx_queue_head);
    return DBUS_OK;
#else
    if ((ret = USB_SUBMIT_URB(req->urb))) {
        ret = DBUS_ERR_TXDROP;
        goto fail;
    }

    dbus_usbos_qenq(&usbos_info->req_txpostedq, req,
                    &usbos_info->txposted_lock);
    atomic_inc(&usbos_info->txposted);

    if (req_zlp != NULL) {
        if ((ret = USB_SUBMIT_URB(req_zlp->urb))) {
            DBUSERR(("failed to submit ZLP URB!\n"));
            ASSERT(0);
            ret = DBUS_ERR_TXDROP;
            goto fail2;
        }

        dbus_usbos_qenq(&usbos_info->req_txpostedq, req_zlp,
                        &usbos_info->txposted_lock);
        /* Also increment txposted for zlp packet, as it will be decremented in
         * dbus_usbos_send_complete()
         */
        atomic_inc(&usbos_info->txposted);
    }

    spin_unlock_irqrestore(&usbos_info->txlock, flags);
    return DBUS_OK;
#endif /* USBOS_TX_THREAD */

fail:
    if (txirb->send_buf != NULL) {
        MFREE(usbos_info->pub->osh, txirb->send_buf, req->buf_len);
        txirb->send_buf = NULL;
        req->buf_len = 0;
    }
    dbus_usbos_qenq(&usbos_info->req_txfreeq, req, &usbos_info->txfree_lock);
#ifndef USBOS_TX_THREAD
fail2:
#endif
    if (req_zlp != NULL) {
        dbus_usbos_qenq(&usbos_info->req_txfreeq, req_zlp,
                        &usbos_info->txfree_lock);
    }

    spin_unlock_irqrestore(&usbos_info->txlock, flags);

#ifndef USBOS_TX_THREAD
    USB_AUTOPM_PUT_INTERFACE_ASYNC(g_probe_info.intf);
#endif /* !USBOS_TX_THREAD */

    return ret;
} /* dbus_usbos_intf_send_irb */

/** Higher layer (dbus_usb.c) recycles a received (and used) packet. */
static int dbus_usbos_intf_recv_irb(void *bus, dbus_irb_rx_t *rxirb)
{
    usbos_info_t *usbos_info = (usbos_info_t *)bus;
    int ret = DBUS_OK;

    if (usbos_info == NULL) {
        return DBUS_ERR;
    }

    ret = dbus_usbos_recv_urb_submit(usbos_info, rxirb, 0);
    return ret;
}

static int dbus_usbos_intf_recv_irb_from_ep(void *bus, dbus_irb_rx_t *rxirb,
                                            uint32 ep_idx)
{
    usbos_info_t *usbos_info = (usbos_info_t *)bus;
    int ret = DBUS_OK;

    if (usbos_info == NULL) {
        return DBUS_ERR;
    }

#ifdef INTR_EP_ENABLE
    /* By specifying the ep_idx value of 0xff, the cdc layer is asking to
     * submit an interrupt URB
     */
    if (rxirb == NULL && ep_idx == 0xff) {
        /* submit intr URB */
        if ((ret = USB_SUBMIT_URB(usbos_info->intr_urb)) < 0) {
            DBUSERR(("%s intr USB_SUBMIT_URB failed, status %d\n", __FUNCTION__,
                     ret));
        }
        return ret;
    }
#else
    if (rxirb == NULL) {
        return DBUS_ERR;
    }
#endif /* INTR_EP_ENABLE */

    ret = dbus_usbos_recv_urb_submit(usbos_info, rxirb, ep_idx);
    return ret;
}

/** Higher layer (dbus_usb.c) want to cancel an IRB */
static int dbus_usbos_intf_cancel_irb(void *bus, dbus_irb_tx_t *txirb)
{
    usbos_info_t *usbos_info = (usbos_info_t *)bus;

    if (usbos_info == NULL) {
        return DBUS_ERR;
    }

    return DBUS_ERR;
}

/** Only one CTL transfer can be pending at any time. This function may block.
 */
static int dbus_usbos_intf_send_ctl(void *bus, uint8 *buf, int len)
{
    usbos_info_t *usbos_info = (usbos_info_t *)bus;
    uint16 size;
#ifndef USBOS_TX_THREAD
    int status;
#endif /* USBOS_TX_THREAD */

    if ((usbos_info == NULL) || (buf == NULL) || (len == 0)) {
        return DBUS_ERR;
    }

    if (usbos_info->ctl_urb == NULL) {
        return DBUS_ERR;
    }

    /* Block until a pending CTL transfer has completed */
    if (down_interruptible(&usbos_info->ctl_lock) != 0) {
        return DBUS_ERR_TXCTLFAIL;
    }

#ifdef USBOS_TX_THREAD
    ASSERT(usbos_info->ctl_state == USBOS_REQUEST_STATE_UNSCHEDULED);
#else
    /* Disable USB autosuspend until this request completes, request USB resume
     * if needed. Because this call runs asynchronously, there is no guarantee
     * the bus is resumed before the URB is submitted, and the URB might be
     * dropped. Use USBOS_TX_THREAD to avoid this.
     */
    USB_AUTOPM_GET_INTERFACE_ASYNC(g_probe_info.intf);
#endif /* USBOS_TX_THREAD */

    size = len;
    usbos_info->ctl_write.wLength = cpu_to_le16p(&size);
    usbos_info->ctl_urb->transfer_buffer_length = size;

    usb_fill_control_urb(usbos_info->ctl_urb, usbos_info->usb,
                         usb_sndctrlpipe(usbos_info->usb, 0),
                         (unsigned char *)&usbos_info->ctl_write, buf, size,
                         (usb_complete_t)dbus_usbos_ctlwrite_complete,
                         usbos_info);

#ifdef USBOS_TX_THREAD
    /* Enqueue CTRL request for transmission by the TX thread. The
     * USB bus will first be resumed if needed.
     */
    usbos_info->ctl_state = USBOS_REQUEST_STATE_SCHEDULED;
    wake_up_interruptible(&usbos_info->usbos_tx_queue_head);
#else
    status = USB_SUBMIT_URB(usbos_info->ctl_urb);
    if (status < 0) {
        DBUSERR(("%s: usb_submit_urb failed %d\n", __FUNCTION__, status));
        up(&usbos_info->ctl_lock);

        USB_AUTOPM_PUT_INTERFACE_ASYNC(g_probe_info.intf);

        return DBUS_ERR_TXCTLFAIL;
    }
#endif /* USBOS_TX_THREAD */

    return DBUS_OK;
} /* dbus_usbos_intf_send_ctl */

/** This function does not seem to be called by anyone, including dbus_usb.c */
static int dbus_usbos_intf_recv_ctl(void *bus, uint8 *buf, int len)
{
    usbos_info_t *usbos_info = (usbos_info_t *)bus;
    int status;
    uint16 size;

    if ((usbos_info == NULL) || (buf == NULL) || (len == 0)) {
        return DBUS_ERR;
    }

    if (usbos_info->ctl_urb == NULL) {
        return DBUS_ERR;
    }

    /* Block until a pending CTRL transfer has completed */
    if (down_interruptible(&usbos_info->ctl_lock) != 0) {
        return DBUS_ERR_TXCTLFAIL;
    }

    /* Disable USB autosuspend until this request completes, request USB resume
     * if needed. */
    USB_AUTOPM_GET_INTERFACE_ASYNC(g_probe_info.intf);

    size = len;
    usbos_info->ctl_read.wLength = cpu_to_le16p(&size);
    usbos_info->ctl_urb->transfer_buffer_length = size;

    if (usbos_info->rxctl_deferrespok) {
        /* BMAC model */
        usbos_info->ctl_read.bRequestType =
            USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_INTERFACE;
        usbos_info->ctl_read.bRequest = DL_DEFER_RESP_OK;
    } else {
        /* full dongle model */
        usbos_info->ctl_read.bRequestType =
            USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE;
        usbos_info->ctl_read.bRequest = 1;
    }

    usb_fill_control_urb(usbos_info->ctl_urb, usbos_info->usb,
                         usb_rcvctrlpipe(usbos_info->usb, 0),
                         (unsigned char *)&usbos_info->ctl_read, buf, size,
                         (usb_complete_t)dbus_usbos_ctlread_complete,
                         usbos_info);

    status = USB_SUBMIT_URB(usbos_info->ctl_urb);
    if (status < 0) {
        DBUSERR(("%s: usb_submit_urb failed %d\n", __FUNCTION__, status));
        up(&usbos_info->ctl_lock);

        USB_AUTOPM_PUT_INTERFACE_ASYNC(g_probe_info.intf);

        return DBUS_ERR_RXCTLFAIL;
    }

    return DBUS_OK;
}

static int dbus_usbos_intf_get_attrib(void *bus, dbus_attrib_t *attrib)
{
    usbos_info_t *usbos_info = (usbos_info_t *)bus;

    if ((usbos_info == NULL) || (attrib == NULL)) {
        return DBUS_ERR;
    }

    attrib->bustype = DBUS_USB;
    attrib->vid = g_probe_info.vid;
    attrib->pid = g_probe_info.pid;
    attrib->devid = 0x4322;

    attrib->nchan = 1;

    /* MaxPacketSize for USB hi-speed bulk out is 512 bytes
     * and 64-bytes for full-speed.
     * When sending pkt > MaxPacketSize, Host SW breaks it
     * up into multiple packets.
     */
    attrib->mtu = usbos_info->maxps;

    return DBUS_OK;
}

/** Called by higher layer (dbus_usb.c) when it wants to 'up' the USB interface
 * to the dongle */
static int dbus_usbos_intf_up(void *bus)
{
    usbos_info_t *usbos_info = (usbos_info_t *)bus;
    uint16 ifnum;
#ifdef BCMUSBDEV_COMPOSITE
    int wlan_if = 0;
#endif
    if (usbos_info == NULL) {
        return DBUS_ERR;
    }

    if (usbos_info->usb == NULL) {
        return DBUS_ERR;
    }

#if defined(INTR_EP_ENABLE)
    /* full dongle use intr EP, bmac doesn't use it */
    if (usbos_info->intr_urb) {
        int ret;

        usb_fill_int_urb(usbos_info->intr_urb, usbos_info->usb,
                         usbos_info->intr_pipe, &usbos_info->intr,
                         usbos_info->intr_size,
                         (usb_complete_t)dbus_usbos_intr_complete, usbos_info,
                         usbos_info->interval);

        if ((ret = USB_SUBMIT_URB(usbos_info->intr_urb))) {
            DBUSERR(("%s USB_SUBMIT_URB failed with status %d\n", __FUNCTION__,
                     ret));
            return DBUS_ERR;
        }
    }
#endif

    if (usbos_info->ctl_urb) {
        usbos_info->ctl_in_pipe = usb_rcvctrlpipe(usbos_info->usb, 0);
        usbos_info->ctl_out_pipe = usb_sndctrlpipe(usbos_info->usb, 0);

#ifdef BCMUSBDEV_COMPOSITE
        wlan_if = dbus_usbos_intf_wlan(usbos_info->usb);
        ifnum = cpu_to_le16(IFDESC(usbos_info->usb, wlan_if).bInterfaceNumber);
#else
        ifnum =
            cpu_to_le16(IFDESC(usbos_info->usb, CONTROL_IF).bInterfaceNumber);
#endif /* BCMUSBDEV_COMPOSITE */
        /* CTL Write */
        usbos_info->ctl_write.bRequestType =
            USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE;
        usbos_info->ctl_write.bRequest = 0;
        usbos_info->ctl_write.wValue = cpu_to_le16(0);
        usbos_info->ctl_write.wIndex = cpu_to_le16p(&ifnum);

        /* CTL Read */
        usbos_info->ctl_read.bRequestType =
            USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE;
        usbos_info->ctl_read.bRequest = 1;
        usbos_info->ctl_read.wValue = cpu_to_le16(0);
        usbos_info->ctl_read.wIndex = cpu_to_le16p(&ifnum);
    }

    /* Success, indicate usbos_info is fully up */
    dbus_usbos_state_change(usbos_info, DBUS_STATE_UP);

    return DBUS_OK;
} /* dbus_usbos_intf_up */

static int dbus_usbos_intf_down(void *bus)
{
    usbos_info_t *usbos_info = (usbos_info_t *)bus;

    if (usbos_info == NULL) {
        return DBUS_ERR;
    }

    dbusos_stop(usbos_info);
    return DBUS_OK;
}

static int dbus_usbos_intf_stop(void *bus)
{
    usbos_info_t *usbos_info = (usbos_info_t *)bus;

    if (usbos_info == NULL) {
        return DBUS_ERR;
    }

    dbusos_stop(usbos_info);
    return DBUS_OK;
}

/** Called by higher layer (dbus_usb.c) */
static int dbus_usbos_intf_set_config(void *bus, dbus_config_t *config)
{
    int err = DBUS_ERR;
    usbos_info_t *usbos_info = bus;

    if (config->config_id == DBUS_CONFIG_ID_RXCTL_DEFERRES) {
        usbos_info->rxctl_deferrespok = config->rxctl_deferrespok;
        err = DBUS_OK;
    } else if (config->config_id == DBUS_CONFIG_ID_AGGR_LIMIT) {
        /* DBUS_CONFIG_ID_AGGR_LIMIT shouldn't be called after probe stage */
        ASSERT(disc_arg == NULL);
        ASSERT(config->aggr_param.maxrxsf > 0);
        ASSERT(config->aggr_param.maxrxsize > 0);
        if (config->aggr_param.maxrxsize > usbos_info->rxbuf_len) {
            int state = usbos_info->pub->busstate;
            dbus_usbos_unlink(&usbos_info->req_rxpostedq,
                              &usbos_info->rxposted_lock);
            while (atomic_read(&usbos_info->rxposted)) {
                DBUSTRACE(("%s rxposted is %d, delay 1 ms\n", __FUNCTION__,
                           atomic_read(&usbos_info->rxposted)));
                dbus_usbos_wait(usbos_info, 1);
            }
            usbos_info->rxbuf_len = config->aggr_param.maxrxsize;
            dbus_usbos_state_change(usbos_info, state);
        }
        err = DBUS_OK;
    }

    return err;
}

/** Called by dbus_usb.c when it wants to download firmware into the dongle */
bool dbus_usbos_dl_cmd(usbos_info_t *usbinfo, uint8 cmd, void *buffer,
                       int buflen)
{
    int transferred;
    int index = 0;
    char *tmpbuf;

    if ((usbinfo == NULL) || (buffer == NULL) || (buflen == 0)) {
        return FALSE;
    }

    tmpbuf = (char *)MALLOC(usbinfo->pub->osh, buflen);
    if (!tmpbuf) {
        DBUSERR(("%s: Unable to allocate memory \n", __FUNCTION__));
        return FALSE;
    }

#ifdef BCM_REQUEST_FW
    if (cmd == DL_GO) {
        index = 1;
    }
#endif

    /* Disable USB autosuspend until this request completes, request USB resume
     * if needed. */
    USB_AUTOPM_GET_INTERFACE(g_probe_info.intf);

    transferred =
        USB_CONTROL_MSG(usbinfo->usb, usb_rcvctrlpipe(usbinfo->usb, 0), cmd,
                        (USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_INTERFACE), 0,
                        index, (void *)tmpbuf, buflen, USB_CTRL_EP_TIMEOUT);
    if (transferred == buflen) {
        memcpy(buffer, tmpbuf, buflen);
    } else {
        DBUSERR(("%s: usb_control_msg failed %d\n", __FUNCTION__, transferred));
    }

    USB_AUTOPM_PUT_INTERFACE(g_probe_info.intf);

    MFREE(usbinfo->pub->osh, tmpbuf, buflen);
    return (transferred == buflen);
}

/**
 * Called by dbus_usb.c when it wants to download a buffer into the dongle (e.g.
 * as part of the download process, when writing nvram variables).
 */
int dbus_write_membytes(usbos_info_t *usbinfo, bool set, uint32 address,
                        uint8 *data, uint size)
{
    hwacc_t hwacc;
    int write_bytes = 4;
    int status;
    int retval = 0;

    DBUSTRACE(("Enter:%s\n", __FUNCTION__));

    /* Read is not supported */
    if (set == 0) {
        DBUSERR(("Currently read is not supported!!\n"));
        return -1;
    }

    USB_AUTOPM_GET_INTERFACE(g_probe_info.intf);

    hwacc.cmd = DL_CMD_WRHW;
    hwacc.addr = address;

    DBUSTRACE(("Address:%x size:%d", hwacc.addr, size));
    do {
        if (size >= 0x4) {
            write_bytes = 0x4;
        } else if (size >= 0x2) {
            write_bytes = 0x2;
        } else {
            write_bytes = 1;
        }

        hwacc.len = write_bytes;

        while (size >= write_bytes) {
            hwacc.data = *((unsigned int *)data);

            status = USB_CONTROL_MSG(
                usbinfo->usb, usb_sndctrlpipe(usbinfo->usb, 0), DL_WRHW,
                (USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_INTERFACE), 1, 0,
                (char *)&hwacc, sizeof(hwacc_t), USB_CTRL_EP_TIMEOUT);
            if (status < 0) {
                retval = -1;
                DBUSERR((" Ctrl write hwacc failed w/status %d @ address:%x \n",
                         status, hwacc.addr));
                goto err;
            }

            hwacc.addr += write_bytes;
            data += write_bytes;
            size -= write_bytes;
        }
    } while (size > 0);

err:
    USB_AUTOPM_PUT_INTERFACE(g_probe_info.intf);

    return retval;
}

int dbus_usbos_readreg(void *bus, uint32 regaddr, int datalen, uint32 *value)
{
    usbos_info_t *usbinfo = (usbos_info_t *)bus;
    int ret = DBUS_OK;
    int transferred;
    uint32 cmd;
    hwacc_t hwacc;

    if (usbinfo == NULL) {
        return DBUS_ERR;
    }

    if (datalen == 1) {
        cmd = DL_RDHW8;
    } else if (datalen == 0x2) {
        cmd = DL_RDHW16;
    } else {
        cmd = DL_RDHW32;
    }

    USB_AUTOPM_GET_INTERFACE(g_probe_info.intf);

    transferred =
        USB_CONTROL_MSG(usbinfo->usb, usb_rcvctrlpipe(usbinfo->usb, 0), cmd,
                        (USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_INTERFACE),
                        (uint16)(regaddr), (uint16)(regaddr >> 16),
                        (void *)&hwacc, sizeof(hwacc_t), USB_CTRL_EP_TIMEOUT);
    if (transferred >= sizeof(hwacc_t)) {
        *value = hwacc.data;
    } else {
        DBUSERR(("%s: usb_control_msg failed %d\n", __FUNCTION__, transferred));
        ret = DBUS_ERR;
    }

    USB_AUTOPM_PUT_INTERFACE(g_probe_info.intf);

    return ret;
}

int dbus_usbos_writereg(void *bus, uint32 regaddr, int datalen, uint32 data)
{
    usbos_info_t *usbinfo = (usbos_info_t *)bus;
    int ret = DBUS_OK;
    int transferred;
    uint32 cmd = DL_WRHW;
    hwacc_t hwacc;

    if (usbinfo == NULL) {
        return DBUS_ERR;
    }

    USB_AUTOPM_GET_INTERFACE(g_probe_info.intf);

    hwacc.cmd = DL_WRHW;
    hwacc.addr = regaddr;
    hwacc.data = data;
    hwacc.len = datalen;

    transferred = USB_CONTROL_MSG(
        usbinfo->usb, usb_sndctrlpipe(usbinfo->usb, 0), cmd,
        (USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_INTERFACE), 1, 0,
        (void *)&hwacc, sizeof(hwacc_t), USB_CTRL_EP_TIMEOUT);
    if (transferred != sizeof(hwacc_t)) {
        DBUSERR(("%s: usb_control_msg failed %d\n", __FUNCTION__, transferred));
        ret = DBUS_ERR;
    }

    USB_AUTOPM_PUT_INTERFACE(g_probe_info.intf);

    return ret;
}

int dbus_usbos_wait(usbos_info_t *usbinfo, uint16 ms)
{
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 0))
    if (in_interrupt()) {
        mdelay(ms);
    } else {
        msleep_interruptible(ms);
    }
#else
    wait_ms(ms);
#endif
    return DBUS_OK;
}

/** Called by dbus_usb.c as part of the firmware download process */
bool dbus_usbos_dl_send_bulk(usbos_info_t *usbinfo, void *buffer, int len)
{
    bool ret = TRUE;
    int status;
    int transferred = 0;

    if (usbinfo == NULL) {
        return DBUS_ERR;
    }

    USB_AUTOPM_GET_INTERFACE(g_probe_info.intf);

    status = USB_BULK_MSG(usbinfo->usb, usbinfo->tx_pipe, buffer, len,
                          &transferred, USB_BULK_EP_TIMEOUT);
    if (status < 0) {
        DBUSERR(("%s: usb_bulk_msg failed %d\n", __FUNCTION__, status));
        ret = FALSE;
    }

    USB_AUTOPM_PUT_INTERFACE(g_probe_info.intf);

    return ret;
}

static bool dbus_usbos_intf_recv_needed(void *bus)
{
    return FALSE;
}

/**
 * Higher layer (dbus_usb.c) wants to execute a function on the condition that
 * the rx spin lock has been acquired.
 */
static void *dbus_usbos_intf_exec_rxlock(void *bus, exec_cb_t cb,
                                         struct exec_parms *args)
{
    usbos_info_t *usbos_info = (usbos_info_t *)bus;
    void *ret;
    unsigned long flags;

    if (usbos_info == NULL) {
        return NULL;
    }

    spin_lock_irqsave(&usbos_info->rxlock, flags);
    ret = cb(args);
    spin_unlock_irqrestore(&usbos_info->rxlock, flags);

    return ret;
}

static void *dbus_usbos_intf_exec_txlock(void *bus, exec_cb_t cb,
                                         struct exec_parms *args)
{
    usbos_info_t *usbos_info = (usbos_info_t *)bus;
    void *ret;
    unsigned long flags;

    if (usbos_info == NULL) {
        return NULL;
    }

    spin_lock_irqsave(&usbos_info->txlock, flags);
    ret = cb(args);
    spin_unlock_irqrestore(&usbos_info->txlock, flags);

    return ret;
}

/**
 * if an error condition was detected in this module, the higher DBUS layer
 * (dbus_usb.c) has to be notified.
 */
int dbus_usbos_errhandler(void *bus, int err)
{
    usbos_info_t *usbos_info = (usbos_info_t *)bus;

    if (usbos_info == NULL) {
        return DBUS_ERR;
    }

    if (usbos_info->cbarg && usbos_info->cbs) {
        if (usbos_info->cbs->errhandler) {
            usbos_info->cbs->errhandler(usbos_info->cbarg, err);
        }
    }

    return DBUS_OK;
}

/**
 * if a change in bus state was detected in this module, the higher DBUS layer
 * (dbus_usb.c) has to be notified.
 */
int dbus_usbos_state_change(void *bus, int state)
{
    usbos_info_t *usbos_info = (usbos_info_t *)bus;

    if (usbos_info == NULL) {
        return DBUS_ERR;
    }

    if (usbos_info->cbarg && usbos_info->cbs) {
        if (usbos_info->cbs->state_change) {
            usbos_info->cbs->state_change(usbos_info->cbarg, state);
        }
    }

    usbos_info->pub->busstate = state;
    return DBUS_OK;
}

int dbus_bus_osl_register(int vid, int pid, probe_cb_t prcb,
                          disconnect_cb_t discb, void *prarg,
                          dbus_intf_t **intf, void *param1, void *param2)
{
    bzero(&g_probe_info, sizeof(probe_info_t));

    probe_cb = prcb;
    disconnect_cb = discb;
    probe_arg = prarg;

    devid_table[0].idVendor = vid;
    devid_table[0].idProduct = pid;

    *intf = &dbus_usbos_intf;

    USB_REGISTER();

    return DBUS_ERR_NODEVICE;
}

int dbus_bus_osl_deregister()
{
    g_probe_info.dereged = TRUE;

    DHD_MUTEX_LOCK();
    if (disconnect_cb && disc_arg && (g_probe_info.disc_cb_done == FALSE)) {
        disconnect_cb(disc_arg);
        disc_arg = NULL;
    }
    DHD_MUTEX_UNLOCK();

    USB_DEREGISTER();

    return DBUS_OK;
}

void *dbus_usbos_intf_attach(dbus_pub_t *pub, void *cbarg,
                             dbus_intf_callbacks_t *cbs)
{
    usbos_info_t *usbos_info;

    if (g_probe_info.dldone == FALSE) {
        DBUSERR(("%s: err device not downloaded!\n", __FUNCTION__));
        return NULL;
    }

    /* Sanity check for BUS_INFO() */
    ASSERT(OFFSETOF(usbos_info_t, pub) == 0);

    usbos_info = MALLOC(pub->osh, sizeof(usbos_info_t));
    if (usbos_info == NULL) {
        return NULL;
    }

    bzero(usbos_info, sizeof(usbos_info_t));

    usbos_info->pub = pub;
    usbos_info->cbarg = cbarg;
    usbos_info->cbs = cbs;

    /* Needed for disconnect() */
    g_probe_info.usbos_info = usbos_info;

    /* Update USB Info */
    usbos_info->usb = g_probe_info.usb;
    usbos_info->rx_pipe = g_probe_info.rx_pipe;
    usbos_info->rx_pipe2 = g_probe_info.rx_pipe2;
    usbos_info->tx_pipe = g_probe_info.tx_pipe;
    usbos_info->intr_pipe = g_probe_info.intr_pipe;
    usbos_info->intr_size = g_probe_info.intr_size;
    usbos_info->interval = g_probe_info.interval;
    usbos_info->pub->device_speed = g_probe_info.device_speed;
    if (usbos_info->rx_pipe2) {
        usbos_info->pub->attrib.has_2nd_bulk_in_ep = 1;
    } else {
        usbos_info->pub->attrib.has_2nd_bulk_in_ep = 0;
    }

    if (usbos_info->tx_pipe) {
        usbos_info->maxps = usb_maxpacket(usbos_info->usb, usbos_info->tx_pipe,
                                          usb_pipeout(usbos_info->tx_pipe));
    }

    INIT_LIST_HEAD(&usbos_info->req_rxfreeq);
    INIT_LIST_HEAD(&usbos_info->req_txfreeq);
    INIT_LIST_HEAD(&usbos_info->req_rxpostedq);
    INIT_LIST_HEAD(&usbos_info->req_txpostedq);
    spin_lock_init(&usbos_info->rxfree_lock);
    spin_lock_init(&usbos_info->txfree_lock);
    spin_lock_init(&usbos_info->rxposted_lock);
    spin_lock_init(&usbos_info->txposted_lock);
    spin_lock_init(&usbos_info->rxlock);
    spin_lock_init(&usbos_info->txlock);

    atomic_set(&usbos_info->rxposted, 0);
    atomic_set(&usbos_info->txposted, 0);

#ifdef USB_DISABLE_INT_EP
    usbos_info->intr_urb = NULL;
#else
    if (!(usbos_info->intr_urb = USB_ALLOC_URB())) {
        DBUSERR(("%s: usb_alloc_urb (tx) failed\n", __FUNCTION__));
        goto fail;
    }
#endif

    if (!(usbos_info->ctl_urb = USB_ALLOC_URB())) {
        DBUSERR(("%s: usb_alloc_urb (tx) failed\n", __FUNCTION__));
        goto fail;
    }

    init_waitqueue_head(&usbos_info->wait);

    if (!(usbos_info->blk_urb =
              USB_ALLOC_URB())) { /* for embedded image downloading */
        DBUSERR(("%s: usb_alloc_urb (tx) failed\n", __FUNCTION__));
        goto fail;
    }

    usbos_info->rxbuf_len = (uint)usbos_info->pub->rxsize;

    atomic_set(&usbos_info->txallocated, 0);
    if (DBUS_OK !=
        dbus_usbos_urbreqs_alloc(usbos_info, usbos_info->pub->ntxq, FALSE)) {
        goto fail;
    }

    atomic_set(&usbos_info->rxallocated, 0);
    if (DBUS_OK != dbus_usbos_urbreqs_alloc(usbos_info,
                                            MIN(DBUS_USB_RXQUEUE_BATCH_ADD, usbos_info->pub->nrxq),
                                            TRUE)) {
        goto fail;
    }

    sema_init(&usbos_info->ctl_lock, 1);

#ifdef USBOS_THREAD
    if (dbus_usbos_thread_init(usbos_info) == NULL) {
        goto fail;
    }
#endif /* USBOS_THREAD */

#ifdef USBOS_TX_THREAD
    if (dbus_usbos_tx_thread_init(usbos_info) == NULL) {
        goto fail;
    }
#endif /* USBOS_TX_THREAD */

    pub->dev_info = g_probe_info.usb;

    return (void *)usbos_info;
fail:
    if (usbos_info->intr_urb) {
        USB_FREE_URB(usbos_info->intr_urb);
        usbos_info->intr_urb = NULL;
    }

    if (usbos_info->ctl_urb) {
        USB_FREE_URB(usbos_info->ctl_urb);
        usbos_info->ctl_urb = NULL;
    }

#if defined(BCM_REQUEST_FW)
    if (usbos_info->blk_urb) {
        USB_FREE_URB(usbos_info->blk_urb);
        usbos_info->blk_urb = NULL;
    }
#endif

    dbus_usbos_urbreqs_free(usbos_info, TRUE);
    atomic_set(&usbos_info->rxallocated, 0);
    dbus_usbos_urbreqs_free(usbos_info, FALSE);
    atomic_set(&usbos_info->txallocated, 0);

    g_probe_info.usbos_info = NULL;

    MFREE(pub->osh, usbos_info, sizeof(usbos_info_t));
    return NULL;
} /* dbus_usbos_intf_attach */

void dbus_usbos_intf_detach(dbus_pub_t *pub, void *info)
{
    usbos_info_t *usbos_info = (usbos_info_t *)info;
    osl_t *osh = pub->osh;

    if (usbos_info == NULL) {
        return;
    }

#ifdef USBOS_TX_THREAD
    dbus_usbos_tx_thread_deinit(usbos_info);
#endif /* USBOS_TX_THREAD */

    /* Must unlink all URBs prior to driver unload;
     * otherwise an URB callback can occur after driver
     * has been de-allocated and rmmod'd
     */
    dbusos_stop(usbos_info);

    if (usbos_info->intr_urb) {
        USB_FREE_URB(usbos_info->intr_urb);
        usbos_info->intr_urb = NULL;
    }

    if (usbos_info->ctl_urb) {
        USB_FREE_URB(usbos_info->ctl_urb);
        usbos_info->ctl_urb = NULL;
    }

    if (usbos_info->blk_urb) {
        USB_FREE_URB(usbos_info->blk_urb);
        usbos_info->blk_urb = NULL;
    }

    dbus_usbos_urbreqs_free(usbos_info, TRUE);
    atomic_set(&usbos_info->rxallocated, 0);
    dbus_usbos_urbreqs_free(usbos_info, FALSE);
    atomic_set(&usbos_info->txallocated, 0);

#ifdef USBOS_THREAD
    dbus_usbos_thread_deinit(usbos_info);
#endif /* USBOS_THREAD */

    g_probe_info.usbos_info = NULL;
    MFREE(osh, usbos_info, sizeof(usbos_info_t));
} /* dbus_usbos_intf_detach */

#ifdef USBOS_TX_THREAD

void *dbus_usbos_tx_thread_init(usbos_info_t *usbos_info)
{
    spin_lock_init(&usbos_info->usbos_tx_list_lock);
    INIT_LIST_HEAD(&usbos_info->usbos_tx_list);
    init_waitqueue_head(&usbos_info->usbos_tx_queue_head);

    usbos_info->usbos_tx_kt =
        kthread_create(dbus_usbos_tx_thread_func, usbos_info, "usb-tx-thread");

    if (IS_ERR(usbos_info->usbos_tx_kt)) {
        DBUSERR(("Thread Creation failed\n"));
        return (NULL);
    }

    usbos_info->ctl_state = USBOS_REQUEST_STATE_UNSCHEDULED;
    wake_up_process(usbos_info->usbos_tx_kt);

    return (usbos_info->usbos_tx_kt);
}

void dbus_usbos_tx_thread_deinit(usbos_info_t *usbos_info)
{
    urb_req_t *req;

    if (usbos_info->usbos_tx_kt) {
        wake_up_interruptible(&usbos_info->usbos_tx_queue_head);
        kthread_stop(usbos_info->usbos_tx_kt);
    }

    /* Move pending requests to free queue so they can be freed */
    while ((req = dbus_usbos_qdeq(&usbos_info->usbos_tx_list,
                                  &usbos_info->usbos_tx_list_lock)) != NULL) {
        dbus_usbos_qenq(&usbos_info->req_txfreeq, req,
                        &usbos_info->txfree_lock);
    }
}

/**
 * Allow USB in-band resume to block by submitting CTRL and DATA URBs on a
 * separate thread.
 */
int dbus_usbos_tx_thread_func(void *data)
{
    usbos_info_t *usbos_info = (usbos_info_t *)data;
    urb_req_t *req;
    dbus_irb_tx_t *txirb;
    int ret;
    unsigned long flags;

#ifdef WL_THREADNICE
    set_user_nice(current, WL_THREADNICE);
#endif

    while (1) {
        /* Wait until there are URBs to submit */
        wait_event_interruptible_timeout(
            usbos_info->usbos_tx_queue_head,
            !list_empty(&usbos_info->usbos_tx_list) ||
                usbos_info->ctl_state == USBOS_REQUEST_STATE_SCHEDULED,
            0x64);

        if (kthread_should_stop()) {
            break;
        }

        /* Submit CTRL URB if needed */
        if (usbos_info->ctl_state == USBOS_REQUEST_STATE_SCHEDULED) {
            /* Disable USB autosuspend until this request completes. If the
             * interface was suspended, this call blocks until it has been
             * resumed.
             */
            USB_AUTOPM_GET_INTERFACE(g_probe_info.intf);

            usbos_info->ctl_state = USBOS_REQUEST_STATE_SUBMITTED;

            ret = USB_SUBMIT_URB(usbos_info->ctl_urb);
            if (ret != 0) {
                DBUSERR(("%s CTRL USB_SUBMIT_URB failed, status %d\n",
                         __FUNCTION__, ret));

                usbos_info->ctl_state = USBOS_REQUEST_STATE_UNSCHEDULED;
                up(&usbos_info->ctl_lock);

                USB_AUTOPM_PUT_INTERFACE_ASYNC(g_probe_info.intf);
            }
        }

        /* Submit all available TX URBs */
        while ((req = dbus_usbos_qdeq(&usbos_info->usbos_tx_list,
                                      &usbos_info->usbos_tx_list_lock)) !=
               NULL) {
            /* Disable USB autosuspend until this request completes. If the
             * interface was suspended, this call blocks until it has been
             * resumed.
             */
            USB_AUTOPM_GET_INTERFACE(g_probe_info.intf);

            spin_lock_irqsave(&usbos_info->txlock, flags);

            ret = USB_SUBMIT_URB(req->urb);
            if (ret == 0) {
                /* URB submitted successfully */
                dbus_usbos_qenq(&usbos_info->req_txpostedq, req,
                                &usbos_info->txposted_lock);
                atomic_inc(&usbos_info->txposted);
            } else {
                /* Submitting the URB failed. */
                DBUSERR(("%s TX USB_SUBMIT_URB failed, status %d\n",
                         __FUNCTION__, ret));

                USB_AUTOPM_PUT_INTERFACE_ASYNC(g_probe_info.intf);
            }

            spin_unlock_irqrestore(&usbos_info->txlock, flags);

            if (ret != 0) {
                /* Cleanup and notify higher layers */
                dbus_usbos_qenq(&usbos_info->req_txfreeq, req,
                                &usbos_info->txfree_lock);

                txirb = req->arg;
                if (txirb->send_buf) {
                    MFREE(usbos_info->pub->osh, txirb->send_buf, req->buf_len);
                    txirb->send_buf = NULL;
                    req->buf_len = 0;
                }

                if (likely(usbos_info->cbarg && usbos_info->cbs)) {
                    if (likely(usbos_info->cbs->send_irb_complete != NULL)) {
                        usbos_info->cbs->send_irb_complete(
                            usbos_info->cbarg, txirb, DBUS_ERR_TXDROP);
                    }
                }
            }
        }
    }

    return 0;
} /* dbus_usbos_tx_thread_func */

#endif /* USBOS_TX_THREAD */

#ifdef USBOS_THREAD

/**
 * Increase system performance by creating a USB thread that runs parallel to
 * other system activity.
 */
static void *dbus_usbos_thread_init(usbos_info_t *usbos_info)
{
    usbos_list_entry_t *entry;
    unsigned long flags, ii;

    spin_lock_init(&usbos_info->usbos_list_lock);
    spin_lock_init(&usbos_info->ctrl_lock);
    INIT_LIST_HEAD(&usbos_info->usbos_list);
    INIT_LIST_HEAD(&usbos_info->usbos_free_list);
    init_waitqueue_head(&usbos_info->usbos_queue_head);
    atomic_set(&usbos_info->usbos_list_cnt, 0);

    for (ii = 0; ii < (usbos_info->pub->nrxq + usbos_info->pub->ntxq); ii++) {
        entry = MALLOC(usbos_info->pub->osh, sizeof(usbos_list_entry_t));
        if (entry) {
            spin_lock_irqsave(&usbos_info->usbos_list_lock, flags);
            list_add_tail((struct list_head *)entry,
                          &usbos_info->usbos_free_list);
            spin_unlock_irqrestore(&usbos_info->usbos_list_lock, flags);
        } else {
            DBUSERR(("Failed to create list\n"));
        }
    }

    usbos_info->usbos_kt =
        kthread_create(dbus_usbos_thread_func, usbos_info, "usb-thread");

    if (IS_ERR(usbos_info->usbos_kt)) {
        DBUSERR(("Thread Creation failed\n"));
        return (NULL);
    }

    wake_up_process(usbos_info->usbos_kt);

    return (usbos_info->usbos_kt);
}

static void dbus_usbos_thread_deinit(usbos_info_t *usbos_info)
{
    struct list_head *cur, *next;
    usbos_list_entry_t *entry;
    unsigned long flags;

    if (usbos_info->usbos_kt) {
        wake_up_interruptible(&usbos_info->usbos_queue_head);
        kthread_stop(usbos_info->usbos_kt);
    }
#if defined(STRICT_GCC_WARNINGS) && defined(__GNUC__)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wcast-qual"
#endif
    list_for_each_safe(cur, next, &usbos_info->usbos_list)
    {
        entry = list_entry(cur, struct usbos_list_entry, list);
        /* detach this entry from the list and then free the entry */
        spin_lock_irqsave(&usbos_info->usbos_list_lock, flags);
        list_del(cur);
        MFREE(usbos_info->pub->osh, entry, sizeof(usbos_list_entry_t));
        spin_unlock_irqrestore(&usbos_info->usbos_list_lock, flags);
    }

    list_for_each_safe(cur, next, &usbos_info->usbos_free_list)
    {
        entry = list_entry(cur, struct usbos_list_entry, list);
        /* detach this entry from the list and then free the entry */
        spin_lock_irqsave(&usbos_info->usbos_list_lock, flags);
        list_del(cur);
        MFREE(usbos_info->pub->osh, entry, sizeof(usbos_list_entry_t));
        spin_unlock_irqrestore(&usbos_info->usbos_list_lock, flags);
    }
#if defined(STRICT_GCC_WARNINGS) && defined(__GNUC__)
#pragma GCC diagnostic pop
#endif
}

/** Process completed URBs in a worker thread */
static int dbus_usbos_thread_func(void *data)
{
    usbos_info_t *usbos_info = (usbos_info_t *)data;
    usbos_list_entry_t *entry;
    struct list_head *cur, *next;
    unsigned long flags;

#ifdef WL_THREADNICE
    set_user_nice(current, WL_THREADNICE);
#endif

    while (1) {
        /* If the list is empty, then go to sleep */
        wait_event_interruptible_timeout(
            usbos_info->usbos_queue_head,
            atomic_read(&usbos_info->usbos_list_cnt) > 0, 0x64);

        if (kthread_should_stop()) {
            break;
        }

        spin_lock_irqsave(&usbos_info->usbos_list_lock, flags);

        /* For each entry on the list, process it.  Remove the entry from
         * the list when done.
         */
#if defined(STRICT_GCC_WARNINGS) && defined(__GNUC__)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wcast-qual"
#endif
        list_for_each_safe(cur, next, &usbos_info->usbos_list)
        {
            urb_req_t *req;
            int len;
            int stat;
            usbos_info_t *usbos_info_local;

            entry = list_entry(cur, struct usbos_list_entry, list);
            if (entry == NULL) {
                break;
            }

            req = entry->urb_context;
            len = entry->urb_length;
            stat = entry->urb_status;
            usbos_info_local = req->usbinfo;

            /* detach this entry from the list and attach it to the free list */
            list_del_init(cur);
            spin_unlock_irqrestore(&usbos_info_local->usbos_list_lock, flags);

            dbus_usbos_recv_complete_handle(req, len, stat);

            spin_lock_irqsave(&usbos_info_local->usbos_list_lock, flags);

            list_add_tail(cur, &usbos_info_local->usbos_free_list);

            atomic_dec(&usbos_info_local->usbos_list_cnt);
        }

        spin_unlock_irqrestore(&usbos_info->usbos_list_lock, flags);
    }
#if defined(STRICT_GCC_WARNINGS) && defined(__GNUC__)
#pragma GCC diagnostic pop
#endif

    return 0;
} /* dbus_usbos_thread_func */

/** Called on Linux calling URB callback, see dbus_usbos_recv_complete() */
static void dbus_usbos_dispatch_schedule(CALLBACK_ARGS)
{
    urb_req_t *req = urb->context;
    usbos_info_t *usbos_info = req->usbinfo;
    usbos_list_entry_t *entry;
    unsigned long flags;
    struct list_head *cur;

    spin_lock_irqsave(&usbos_info->usbos_list_lock, flags);

    cur = usbos_info->usbos_free_list.next;
#if defined(STRICT_GCC_WARNINGS) && defined(__GNUC__)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wcast-qual"
#endif
    entry = list_entry(cur, struct usbos_list_entry, list);
#if defined(STRICT_GCC_WARNINGS) && defined(__GNUC__)
#pragma GCC diagnostic pop
#endif

    /* detach this entry from the free list and prepare it insert it to use list
     */
    list_del_init(cur);

    if (entry) {
        entry->urb_context = urb->context;
        entry->urb_length = urb->actual_length;
        entry->urb_status = urb->status;

        atomic_inc(&usbos_info->usbos_list_cnt);
        list_add_tail(cur, &usbos_info->usbos_list);
    } else {
        DBUSERR(("!!!!!!OUT OF MEMORY!!!!!!!\n"));
    }

    spin_unlock_irqrestore(&usbos_info->usbos_list_lock, flags);

    /* thread */
    wake_up_interruptible(&usbos_info->usbos_queue_head);
} /* dbus_usbos_dispatch_schedule */

#endif /* USBOS_THREAD */

#ifdef BCM_REQUEST_FW

struct request_fw_context {
    const struct firmware *firmware;
    struct semaphore lock;
};

/*
 * Callback for dbus_request_firmware().
 */
static void dbus_request_firmware_done(const struct firmware *firmware,
                                       void *ctx)
{
    struct request_fw_context *context = (struct request_fw_context *)ctx;

    /* Store the received firmware handle in the context and wake requester */
    context->firmware = firmware;
    up(&context->lock);
}

/*
 * Send a firmware request and wait for completion.
 *
 * The use of the asynchronous version of request_firmware() is needed to avoid
 * kernel oopses when we just come out of system hibernate.
 */
static int dbus_request_firmware(const char *name,
                                 const struct firmware **firmware)
{
    struct request_fw_context *context;
    int ret;

    context = kzalloc(sizeof(*context), GFP_KERNEL);
    if (!context) {
        return -ENOMEM;
    }

    sema_init(&context->lock, 0);

    ret = request_firmware_nowait(THIS_MODULE, true, name,
                                  &g_probe_info.usb->dev, GFP_KERNEL, context,
                                  dbus_request_firmware_done);
    if (ret) {
        kfree(context);
        return ret;
    }

    /* Wait for completion */
    if (down_interruptible(&context->lock) != 0) {
        kfree(context);
        return -ERESTARTSYS;
    }

    *firmware = context->firmware;
    kfree(context);

    return *firmware != NULL ? 0 : -ENOENT;
}

static void *dbus_get_fwfile(int devid, int chiprev, uint8 **fw, int *fwlen,
                             uint16 boardtype, uint16 boardrev)
{
    const struct firmware *firmware = NULL;
#ifndef OEM_ANDROID
    s8 *device_id = NULL;
    s8 *chip_rev = "";
#endif /* OEM_ANDROID */
    s8 file_name[64];
    int ret;

#ifndef OEM_ANDROID
    switch (devid) {
        case BCM4350_CHIP_ID:
        case BCM4354_CHIP_ID:
        case BCM43556_CHIP_ID:
        case BCM43558_CHIP_ID:
        case BCM43566_CHIP_ID:
        case BCM43568_CHIP_ID:
        case BCM43570_CHIP_ID:
        case BCM4358_CHIP_ID:
            device_id = "4350";
            break;
        case BCM43143_CHIP_ID:
            device_id = "43143";
            break;
        case BCM43234_CHIP_ID:
        case BCM43235_CHIP_ID:
        case BCM43236_CHIP_ID:
            device_id = "43236";
            break;
        case BCM43242_CHIP_ID:
            device_id = "43242";
            break;
        case BCM43238_CHIP_ID:
            device_id = "43238";
            break;
        case BCM43526_CHIP_ID:
            device_id = "43526";
            break;
        case BCM43569_CHIP_ID:
            device_id = "43569";
            switch (chiprev) {
                case 0:
                    chip_rev = "a0";
                    break;
                case 0x2:
                    chip_rev = "a2";
                    break;
                default:
                    break;
            }
            break;
        default:
            DBUSERR(("unsupported device %x\n", devid));
            return NULL;
    }

    /* Load firmware */
    snprintf(file_name, sizeof(file_name), "brcm/bcm%s%s-firmware.bin",
             device_id, chip_rev);
#else
    snprintf(file_name, sizeof(file_name), "%s", CONFIG_ANDROID_BCMDHD_FW_PATH);
#endif /* OEM_ANDROID */

    ret = dbus_request_firmware(file_name, &firmware);
    if (ret) {
        DBUSERR(("fail to request firmware %s\n", file_name));
        return NULL;
    }

    *fwlen = firmware->size;
    *fw = (uint8 *)firmware->data;
    return (void *)firmware;
}

static void *dbus_get_nvfile(int devid, int chiprev, uint8 **fw, int *fwlen,
                             uint16 boardtype, uint16 boardrev)
{
    const struct firmware *firmware = NULL;
#ifndef OEM_ANDROID
    s8 *device_id = NULL;
    s8 *chip_rev = "";
#endif /* OEM_ANDROID */
    s8 file_name[64];
    int ret;

#ifndef OEM_ANDROID
    switch (devid) {
        case BCM4350_CHIP_ID:
        case BCM4354_CHIP_ID:
        case BCM43556_CHIP_ID:
        case BCM43558_CHIP_ID:
        case BCM43566_CHIP_ID:
        case BCM43568_CHIP_ID:
        case BCM43570_CHIP_ID:
        case BCM4358_CHIP_ID:
            device_id = "4350";
            break;
        case BCM43143_CHIP_ID:
            device_id = "43143";
            break;
        case BCM43234_CHIP_ID:
            device_id = "43234";
            break;
        case BCM43235_CHIP_ID:
            device_id = "43235";
            break;
        case BCM43236_CHIP_ID:
            device_id = "43236";
            break;
        case BCM43238_CHIP_ID:
            device_id = "43238";
            break;
        case BCM43242_CHIP_ID:
            device_id = "43242";
            break;
        case BCM43526_CHIP_ID:
            device_id = "43526";
            break;
        case BCM43569_CHIP_ID:
            device_id = "43569";
            switch (chiprev) {
                case 0:
                    chip_rev = "a0";
                    break;
                case 0x2:
                    chip_rev = "a2";
                    break;
                default:
                    break;
            }
            break;
        default:
            DBUSERR(("unsupported device %x\n", devid));
            return NULL;
    }

    /* Load board specific nvram file */
    snprintf(file_name, sizeof(file_name), "brcm/bcm%s%s-%2x-%2x.nvm",
             device_id, chip_rev, boardtype, boardrev);
#else
    snprintf(file_name, sizeof(file_name), "%s",
             CONFIG_ANDROID_BCMDHD_NVRAM_PATH);
#endif /* OEM_ANDROID */

    ret = dbus_request_firmware(file_name, &firmware);
    if (ret) {
        DBUSERR(("fail to request nvram %s\n", file_name));

#ifndef OEM_ANDROID
        /* Load generic nvram file */
        snprintf(file_name, sizeof(file_name), "brcm/bcm%s%s.nvm", device_id,
                 chip_rev);

        ret = dbus_request_firmware(file_name, &firmware);
#endif /* OEM_ANDROID */
        if (ret) {
            DBUSERR(("fail to request nvram %s\n", file_name));
            return NULL;
        }
    }

    *fwlen = firmware->size;
    *fw = (uint8 *)firmware->data;
    return (void *)firmware;
}

void *dbus_get_fw_nvfile(int devid, int chiprev, uint8 **fw, int *fwlen,
                         int type, uint16 boardtype, uint16 boardrev)
{
    switch (type) {
        case DBUS_FIRMWARE:
            return dbus_get_fwfile(devid, chiprev, fw, fwlen, boardtype,
                                   boardrev);
        case DBUS_NVFILE:
            return dbus_get_nvfile(devid, chiprev, fw, fwlen, boardtype,
                                   boardrev);
        default:
            return NULL;
    }
}

void dbus_release_fw_nvfile(void *firmware)
{
    release_firmware((struct firmware *)firmware);
}
#endif /* BCM_REQUEST_FW */

#ifdef BCMUSBDEV_COMPOSITE
/**
 * For a composite device the interface order is not guaranteed, scan the device
 * struct for the WLAN interface.
 */
static int dbus_usbos_intf_wlan(struct usb_device *usb)
{
    int i, num_of_eps, ep, intf_wlan = -1;
    int num_intf = CONFIGDESC(usb)->bNumInterfaces;
    struct usb_endpoint_descriptor *endpoint;

    for (i = 0; i < num_intf; i++) {
        if (IFDESC(usb, i).bInterfaceClass != USB_CLASS_VENDOR_SPEC) {
            continue;
        }
        num_of_eps = IFDESC(usb, i).bNumEndpoints;

        for (ep = 0; ep < num_of_eps; ep++) {
            endpoint = &IFEPDESC(usb, i, ep);
            if ((endpoint->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) ==
                USB_ENDPOINT_XFER_BULK) {
                intf_wlan = i;
                break;
            }
        }
        if (ep < num_of_eps) {
            break;
        }
    }

    return intf_wlan;
}
#endif /* BCMUSBDEV_COMPOSITE */
